Syntax
I. Core Syntax and Basic Concepts
This section introduces the engine’s foundational syntax units: Constant, Concept, Variable, Operator, CompoundTerm, Assertion, Formula, Rule, and ConflictRule.
1. Constant
A constant represents a specific individual entity. It must belong to at least one given concept (Concept) and is an indivisible basic unit.
Code form:
from kele.syntax import Constant
constant_1 = Constant('constant_1', concept_1)
# Declare a constant named constant_1 that belongs to concept_1String form:
WIPDescription field:
Constant(..., description="...") is supported.
See Description behavior across syntax levels.
2. Concept
A concept is a set of constants or concepts that share some common property.
Code form:
from kele.syntax import Concept
concept_1 = Concept('concept_1') # Declare a concept named concept_1String form:
WIPDescription field:
Concept(..., description="...") is supported.
See Description behavior across syntax levels.
2.1 Registering Subsumption (Subset) Relations
In real problems, concepts often form hierarchies, e.g. int ⊆ real, rational ⊆ real. If an operator parameter expects real, passing an int should be treated as type-compatible.
- Single relation: Maintained by a function on the
Conceptclass
Concept.add_subsumption("int", "real")- Batch list: A wrapper around
add_subsumption
add_subsumptions([
("int", "real"),
("rational", "real"),
])- Mapping (child -> list of parents): A wrapper around
add_subsumption
add_subsumptions_from_mapping({
"int": ["real"],
"rational": ["real"],
})- String DSL (supports
⊆and<=; separators: comma / semicolon / newline): A wrapper aroundadd_subsumption
add_subsumptions_from_string("""
int ⊆ real, rational <= real;
positive_int <= int
""")- Specify parent concepts at construction time:
Concept("int", parents=["real"])- Chain-style setting of parent concepts:
Concept("int").set_parents(["real"])TIP
All of the above approaches can be mixed. Duplicate declarations are automatically de-duplicated.
Example: registering subsumption relations
Real = Concept("real")
Int = Concept("int", parents=["real"])
PosInt = Concept("positive_int", parents=["int"])
to_real = Operator("to_real", input_concepts=["int"], output_concept="real")
# Expects int; passing positive_int is also OK (since positive_int ⊆ int)
t1 = CompoundTerm("to_real", [Constant(5, "positive_int")]) # OK
t2 = CompoundTerm("to_real", [Constant(5, "real")]) # Raises an exception
register_concept_relations("int ⊆ real")
# Attempting to register a reverse edge will raise an error
try:
Concept.add_subsumption("real", "int")
except ValueError as e:
print("Prevented mutual subsumption:", e)3. Variable
A variable is a placeholder in logical expressions, used to refer to unknown or yet-to-be-determined objects. Variables are allowed only in rules and queries and must not appear in facts stored in the FactBase.
Code form:
from kele.syntax import Variable
variable_1 = Variable('variable_1') # Declare a variable named variable_1TIP
Tip: Variables with the same name are considered equal (hashed/compared by name), even if they are different object instances.
String form:
WIPDescription field:
Variable does not provide a description field.
4. Operator
An operator represents a relation or computation over constants and concepts. When defining an operator, you must specify:
- The list of input-parameter concepts:
input_concepts - The output concept:
output_concept(restricted to exactly one)
Code form:
from kele.syntax import Operator
operator_1 = Operator(
'operator_1',
input_concepts=[concept_1, concept_2],
output_concept=concept_3
)
# Declare an operator named operator_1,
# with input concepts concept_1 and concept_2, and output concept concept_3String form:
WIPDescription field:
Operator(..., description="...") is supported.
See Description behavior across syntax levels.
4.1 Action on Operator (Operators with External Implementations)
Operator can also take an implement_func argument to provide an external implementation (hereafter “executable operator”). In that case, the operator’s output is computed by implement_func and does not need to be explicitly stored in the FactBase.
Code form:
from kele.syntax import Operator
def action_func(term):
# term is a FlatCompoundTerm; read term.arguments and compute
# Return value must be a TERM_TYPE (usually Constant or FlatCompoundTerm),
# and must satisfy output_concept
return result
action_op = Operator(
name="action_op",
input_concepts=[input_concept1, input_concept2],
output_concept=output_concept,
implement_func=action_func,
)WARNING
For executable operators, the corresponding CompoundTerm currently must be a FlatCompoundTerm (introduced below). Full CompoundTerm support is not yet available and will be opened up in later versions.
TIP
If a Rule contains a CompoundTerm with an executable operator, then all Variables in that CompoundTerm must also appear in other Assertions that do not contain executable operators.
5. CompoundTerm
A compound term represents an operator applied to a list of arguments. The elements in the argument tuple can be:
ConstantVariable- Other
CompoundTerms
Code form:
from kele.syntax import CompoundTerm
compoundterm_1 = CompoundTerm(operator_1, [constant_1, variable_1])
# Compound term with operator_1 and arguments (constant_1, variable_1)
compoundterm_2 = CompoundTerm(operator_2, [compoundterm_1, constant_2])
# Requirement: operator_1's output concept == operator_2's first input conceptString form:
WIPDescription field:
CompoundTerm(..., description="...") is supported.
See Description behavior across syntax levels.
TIP
Well-formedness requirement: For a well-formed CompoundTerm, the concept of each argument (or the output concept of a nested compound term) must match the corresponding entry in the Operator’s input_concepts, position by position.
5.1 FlatCompoundTerm (Atomic Compound Term)
An atomic compound term is a compound term whose arguments do not contain any other CompoundTerms.
Code form:
from kele.syntax import FlatCompoundTerm
atom_compoundterm_1 = FlatCompoundTerm(operator_1, [constant_1, variable_1])
# Atomic compound term with operator_1 and arguments (constant_1, variable_1)String form:
WIPDescription field:
FlatCompoundTerm(..., description="...") is supported.
See Description behavior across syntax levels.
Usually you do not need to manually create FlatCompoundTerm; the engine will automatically convert a CompoundTerm to a FlatCompoundTerm when conditions are met.
6. Assertion
An assertion is the basic unit of knowledge, stating that “the left-hand side and the right-hand side refer to the same object/value”.
Code form:
from kele.syntax import Assertion
assertion_1 = Assertion(compoundterm_1, compoundterm_2)
# Assert that compoundterm_1 equals compoundterm_2String form:
WIPDescription field:
Assertion(..., description="...") is supported.
See Description behavior across syntax levels.
7. Formula
A formula is composed of one or more Assertions connected by logical connectives. Supported connectives include:
'AND''OR''NOT''IMPLIES''IFF'
Legacy 'EQUAL' input is still accepted for compatibility, but it is deprecated.
TIP
Boolean usage: Assertion and Formula are symbolic objects and cannot be used as Python booleans. Their __bool__ method raises TypeError to avoid confusing Python truthiness (e.g., “non-empty is True”) with the logical truth of an assertion/formula. Always evaluate them explicitly in the engine instead.
Code form:
from kele.syntax import Formula
formula_1 = Formula(assertion_1, 'AND', assertion_2)
# Represents: assertion_1 AND assertion_2
formula_2 = Formula(formula_1, 'OR', assertion_3)
# Represents: (assertion_1 AND assertion_2) OR assertion_3String form:
WIPDescription field:
Formula(..., description="...") is supported.
See Description behavior across syntax levels.
8. Rule
A rule consists of a condition formula (body) and a conclusion formula or assertion (head). The inference engine derives new facts from known facts using rules. You can set a rule priority (priority) to control execution order.
Code form:
from kele.syntax import Rule
rule_1 = Rule(assertion_3, formula_1)
# If formula_1 holds, then assertion_3 holds as well
# (Note: constructor argument order is head, body)String form:
WIPTIP
- The constructor argument order for
RuleisRule(head, body, ...). Using keyword arguments (Rule(head=..., body=...)) is recommended to avoid mistakes. - Variables are allowed in
CompoundTerm/Assertiononly insideRules. Facts in the FactBase must not contain variables. - Internally, the engine converts
Formulain a rule into a clause list via DNF (conjunctions containing onlyAssertionandNOT Assertion) and splits it into multiple sub-rules; thereforeFormulamainly serves as syntactic sugar and does not cover the full semantics of all logical connectives. - The rule head supports only a single
Assertionor a conjunction ofAssertions connected only byAND.
Description field:
Rule(..., description="...") is supported.
See Description behavior across syntax levels.
Description behavior across syntax levels
description is reader-facing text only. It does not change inference results.
Supported syntax structures:
Constant,Concept,OperatorCompoundTerm,FlatCompoundTermAssertion,FormulaRule,ConflictRule
Default behavior:
- If not provided,
descriptiondefaults to"". - The engine does not auto-merge descriptions from child structures.
Priority when text is resolved:
- Explicit constructor value:
description="..." - Auto description function set by
set_description_handler(...)(only on supported structures; see Section II) - Fallback empty string
""
Name reuse behavior:
ConceptandOperatorare singletons by name. Recreating with emptydescriptionkeeps the existing text; recreating with a different non-empty text raises an error.Constantis not singleton. If multiple constants share the same symbol, name-based reading returns the latest recorded one.
II. Special Syntax
Description-related syntax in this section:
- Read description by name (Constant / Concept / Operator)
- Auto description function (term / assertion / formula / rule)
1. Intro: Presence/Introduction Marker
Intro(T) is used to indicate whether an instance of a CompoundTerm appears in some assertion in the FactBase.
Code form:
from kele.syntax import Intro, CompoundTerm
compoundterm_1 = CompoundTerm(operator_1, [constant_1, variable_1])
I1 = Intro(compoundterm_1)
# I1 is true iff there exists an instance of the form
# CompoundTerm(operator_1, [constant_1, any_constant])
# that appears as a CompoundTerm in some Assertion in the FactBaseString form:
WIP2. ConflictRule: checking rule
ConflictRule is intended for checking tasks: once an undesired state is derived, the current inference run stops immediately. It is commonly used in CI-style checks (for example, rule-consistency checks or forbidden-pattern checks).
Code form:
from kele.syntax import ConflictRule
conflict_rule = ConflictRule(
body=[
Assertion(CompoundTerm(parent_op, [X, Y]), true_const),
Assertion(CompoundTerm(parent_op, [Y, X]), true_const),
],
name="no_mutual_parent",
)You can use it together with normal rules:
rules = [normal_rule_1, normal_rule_2, conflict_rule]Runtime behavior:
- When
ConflictRule.bodyis derived, the engine returnsInferenceStatus.CONFLICT_DETECTEDand terminates. - The termination reason is available in
EngineRunResult.conflict_reason(includingrule_name,rule_body, andevidence).
Description field:
ConflictRule(..., description="...") is supported.
3. Read description by name (Constant / Concept / Operator)
For Constant, Concept, and Operator, you can read description text by name.
How to use:
- Create the engine with
enable_description_registry=True. - Define
descriptionwhen creatingConcept,Operator, andConstant. - Call
engine.get_description_by_key(...).
from kele.main import InferenceEngine
from kele.syntax import Concept, Operator, Constant
from kele.knowledge_bases.builtin_base.builtin_concepts import BOOL_CONCEPT
engine = InferenceEngine(facts=[], rules=[], enable_description_registry=True)
person = Concept("Person", description="All person entities")
parent = Operator("parent", [person, person], BOOL_CONCEPT, description="Parent relation")
alice = Constant("Alice", person, description="Sample person")
engine.get_description_by_key("Person") # "All person entities"
engine.get_description_by_key("parent") # "Parent relation"
engine.get_description_by_key("Alice") # "Sample person"If one name is used in multiple categories, specify registry_type:
engine.get_description_by_key("Person", registry_type="concept")
# registry_type can be: "constant" | "concept" | "operator"4. Auto description function (term / assertion / formula / rule)
Some syntax structures can auto-generate description when you do not pass description="...".
Supported structures:
CompoundTerm/FlatCompoundTermAssertionFormulaRule(andConflictRule, viaRule)
You set it with set_description_handler(...):
from kele.syntax import CompoundTerm, Concept, Constant, Operator
from kele.syntax.mixins import SupportsDescription
concept = Concept("Thing", description="A concept for demo")
operator = Operator("tag", [concept], concept, description="Tag operator")
constant = Constant("Alice", concept, description="A sample constant")
def auto_desc(obj: SupportsDescription) -> str:
if isinstance(obj, CompoundTerm):
return f"custom:{obj.operator.name}"
return "custom"
CompoundTerm.set_description_handler(auto_desc)
term_1 = CompoundTerm(operator, [constant]) # no explicit description
term_2 = CompoundTerm(operator, [constant], description="Manual text")
term_1.description # "custom:tag"
term_2.description # "Manual text" (explicit text wins)
CompoundTerm.set_description_handler(None) # resetConstant, Concept, and Operator do not use this auto function; they use the constructor text directly.
5. QueryStructure
QueryStructure specifies a query problem for the inference engine. You need to provide:
premises: a list of premise factsquestion: a list of formulas or assertions to be solved; multiple formulas/assertions are treated as a conjunction (i.e., they must all hold)
Code form:
from kele.main import QueryStructure
querystructure_1 = QueryStructure(
premises=fact_list, # A list containing multiple Facts
question=formula_2 # The question to solve
)String form:
WIPIII. Built-in Syntax and Built-in Operators
1. Built-in Concepts
Several built-in concepts are defined in kele.knowledge_bases.builtin_base.builtin_concepts:
FREEVARANY: a placeholder concept. It should not be used in external APIs and is compatible with any Concept.DANGER
Defining a custom
"FREEVARANY"concept will be rejected. Do not force-define it, or you may break placeholder behavior.BOOL_CONCEPT: the Boolean concept. All Boolean values should belong to this concept and use the presettrue_constandfalse_const.COMPLEX_NUMBER_CONCEPT: the complex-number concept.EQUATION_CONCEPT: the arithmetic-equation concept.
2. Built-in Constants
true_const: representsTruefalse_const: representsFalse
3. Built-in Operators
The following arithmetic-related operators are provided in kele.knowledge_bases.builtin_base.builtin_operators. They all operate on complex numbers (belonging to COMPLEX_NUMBER_CONCEPT):
arithmetic_plus_op: additionarithmetic_minus_op: subtractionarithmetic_times_op: multiplicationarithmetic_divide_op: divisionarithmetic_negate_op: negationarithmetic_equation_op: arithmetic equality, producing values inEQUATION_CONCEPT(for example, it is the built-in operator behind expressions like(1 + 2) = 3)
Items 1-5 above are executable operators, and their results are computed by implementation functions. arithmetic_equation_op is the built-in equality operator for arithmetic expressions.
IV. Safety
To ensure the inference engine runs correctly, rules and facts must satisfy the following safety constraints.
1. Fact Safety
An Assertion used as a Fact must not contain Variables (including initial facts and the premise facts in QueryStructure).
2. Rule Safety
For unsafe rules, the engine will proactively add Intro (and raise a warning) to ensure smooth usage. However, this may slow execution; users are advised to understand this section and manually optimize rules. For readability by non-engine specialists, safety is defined below in a segmented (non-recursive) way.
- Assign a boolean value T/F to each
Assertionin a rule body. Consider all assignments that can make the body true. If anAssertionis true under all such assignments, call it a T-typeAssertion. - Every
Variableappearing in the rule should appear in some T-typeAssertion. - Variables inside a
CompoundTermcontaining an executable operator must appear in at least one T-typeAssertionthat does not contain executable operators. - Any
CompoundTermcontaining an executable operator must be aFlatCompoundTerm.
Examples
- Safe rule example:
r(X) = r(Y) AND h(X) = h(Y) -> g(X) = 1- Unsafe example 1:
r(X) = r(Y) OR h(Z) = h(Y) -> g(X) = 1Reason: In the disjunctive branch h(Z) = h(Y), the variable X in the head does not appear.
- Unsafe example 2:
r(X) = r(Y) AND NOT(h(Z) = h(Y)) -> g(X) = 1Reason: Variable Z appears only in a negated Assertion and does not appear in any non-negated Assertion.