diff --git a/pyds/__init__.py b/pyds/__init__.py index 981f34b..4b959a5 100644 --- a/pyds/__init__.py +++ b/pyds/__init__.py @@ -1,3 +1,8 @@ +"""Python wrapper for a deductive system implemented in C++. + +Provides classes and functions for working with logical terms, rules, and inference. +""" + __all__ = [ "buffer_size", "scoped_buffer_size", diff --git a/pyds/buffer_size.py b/pyds/buffer_size.py index 19fe338..f164136 100644 --- a/pyds/buffer_size.py +++ b/pyds/buffer_size.py @@ -1,3 +1,5 @@ +"""Buffer size management for the deductive system.""" + __all__ = [ "buffer_size", "scoped_buffer_size", @@ -9,6 +11,20 @@ def buffer_size(size: int = 0) -> int: + """Gets the current buffer size, or sets a new buffer size and returns the previous value. + + The buffer size is used for internal operations like conversions and transformations. + + Args: + size: The new buffer size to set. If 0 (default), the current size is returned without modification. + + Returns: + The previous buffer size value. + + Example: + >>> current_size = buffer_size() # Get current size + >>> old_size = buffer_size(2048) # Set new size, returns old size + """ global _buffer_size old_buffer_size = _buffer_size if size > 0: @@ -18,6 +34,20 @@ def buffer_size(size: int = 0) -> int: @contextmanager def scoped_buffer_size(size: int = 0): + """Context manager for temporarily changing the buffer size. + + Sets the buffer size for the duration of the context and restores the + previous value when exiting. + + Args: + size: The temporary buffer size to set. + + Example: + >>> with scoped_buffer_size(4096): + ... # Operations here use buffer size of 4096 + ... pass + >>> # Buffer size is restored to previous value + """ old_buffer_size = buffer_size(size) try: yield diff --git a/pyds/common.py b/pyds/common.py index 323f269..4cfbd17 100644 --- a/pyds/common.py +++ b/pyds/common.py @@ -1,3 +1,5 @@ +"""Base class for all deductive system wrapper types.""" + from __future__ import annotations __all__ = [ @@ -11,9 +13,24 @@ class Common(typing.Generic[T]): + """Base class for all deductive system wrapper types. + + Handles initialization, serialization, and common operations. + """ + _base: type[T] def __init__(self, value: Common[T] | T | str | bytes, size: int | None = None) -> None: + """Creates a new instance. + + Args: + value: Initial value (can be another instance, base value, string, or memoryview). + size: Optional buffer capacity for the internal storage. + + Raises: + ValueError: If initialization fails or invalid arguments are provided. + TypeError: If value is of an unsupported type. + """ self.value: T self.capacity: int | None if isinstance(value, type(self)): @@ -38,6 +55,14 @@ def __init__(self, value: Common[T] | T | str | bytes, size: int | None = None) raise TypeError("Unsupported type for initialization.") def __str__(self) -> str: + """Convert the value to a string representation. + + Returns: + The string representation. + + Raises: + ValueError: If conversion fails. + """ result = self._base.to_string(self.value, buffer_size()) if result == "": raise ValueError("Conversion to string failed.") @@ -47,12 +72,27 @@ def __repr__(self) -> str: return f"{type(self).__name__}[{self}]" def data(self) -> bytes: + """Get the binary representation of the value. + + Returns: + The binary data as bytes. + """ return self._base.to_binary(self.value) def size(self) -> int: + """Get the size of the data in bytes. + + Returns: + The data size. + """ return self.value.data_size() def __copy__(self) -> Common[T]: + """Create a deep copy of this instance. + + Returns: + A new instance with cloned value. + """ return type(self)(self.value.clone(), self.size()) def __hash__(self) -> int: diff --git a/pyds/item_t.py b/pyds/item_t.py index 54f2df2..b258b07 100644 --- a/pyds/item_t.py +++ b/pyds/item_t.py @@ -1,3 +1,5 @@ +"""Wrapper class for items in the deductive system.""" + __all__ = [ "Item", ] @@ -8,8 +10,22 @@ class Item(Common[ds.Item]): + """Wrapper class for items in the deductive system. + + Items represent constants or functors in logical terms. + + Example: + >>> item = Item("atom") + >>> print(item.name) # "atom" + """ + _base = ds.Item @property def name(self) -> String: + """Get the name of this item. + + Returns: + The item name as a String. + """ return String(self.value.name()) diff --git a/pyds/list_t.py b/pyds/list_t.py index c6d2bba..cdb7472 100644 --- a/pyds/list_t.py +++ b/pyds/list_t.py @@ -1,3 +1,5 @@ +"""Wrapper class for lists in the deductive system.""" + from __future__ import annotations __all__ = [ @@ -13,12 +15,35 @@ class List(Common[ds.List]): + """Wrapper class for lists in the deductive system. + + Lists contain ordered sequences of terms. + + Example: + >>> lst = List("(a b c)") + >>> print(len(lst)) # 3 + >>> print(lst[0]) # "a" + """ + _base = ds.List def __len__(self) -> int: + """Get the number of elements in the list. + + Returns: + The list length. + """ return len(self.value) def __getitem__(self, index: int) -> Term: + """Get an element from the list by index. + + Args: + index: The zero-based index of the element. + + Returns: + The term at the specified index. + """ from .term_t import Term return Term(self.value[index]) diff --git a/pyds/rule_t.py b/pyds/rule_t.py index f485a88..d49d82a 100644 --- a/pyds/rule_t.py +++ b/pyds/rule_t.py @@ -1,3 +1,5 @@ +"""Wrapper class for logical rules in the deductive system.""" + from __future__ import annotations __all__ = [ @@ -11,22 +13,73 @@ class Rule(Common[ds.Rule]): + """Wrapper class for logical rules in the deductive system. + + A rule consists of zero or more premises (above the line) and a conclusion (below the line). + + Example: + >>> rule = Rule("(father `X `Y)\\n----------\\n(parent `X `Y)\\n") + >>> print(rule.conclusion) # "(parent `X `Y)" + >>> print(len(rule)) # 1 (number of premises) + """ + _base = ds.Rule def __len__(self) -> int: + """Get the number of premises in the rule. + + Returns: + The number of premises. + """ return len(self.value) def __getitem__(self, index: int) -> Term: + """Get a premise term by index. + + Args: + index: The zero-based index of the premise. + + Returns: + The premise term at the specified index. + """ return Term(self.value[index]) @property def conclusion(self) -> Term: + """Get the conclusion of the rule. + + Returns: + The conclusion term. + """ return Term(self.value.conclusion()) def __floordiv__(self, other: Rule) -> Rule | None: return self.ground(other) def ground(self, other: Rule, scope: str | None = None) -> Rule | None: + """Ground this rule using a dictionary to substitute variables with values. + + Args: + other: A rule representing a dictionary (list of pairs). Each pair contains + a variable and its substitution value. + Example: Rule("((`a b))") means substitute variable `a with value b. + scope: Optional scope string for variable scoping. + + Returns: + The grounded rule, or None if grounding fails. + + Example: + >>> a = Rule("`a") + >>> b = Rule("((`a b))") + >>> str(a.ground(b)) + '----\\nb\\n' + >>> + >>> # With scope + >>> c = Rule("`a") + >>> d = Rule("((x y `a `b) (y x `b `c))") + >>> str(c.ground(d, "x")) + '----\\n`c\\n' + """ capacity = buffer_size() rule = ds.Rule.ground(self.value, other.value, scope, capacity) if rule is None: @@ -34,6 +87,24 @@ def ground(self, other: Rule, scope: str | None = None) -> Rule | None: return Rule(rule, capacity) def __matmul__(self, other: Rule) -> Rule | None: + """Match this rule with another rule using unification. + + This is the operator form of the match method, using the @ operator. + This unifies the first premise of this rule with the other rule. + The other rule must be a fact (a rule without premises). + + Args: + other: The rule to match against (must be a fact without premises). + + Returns: + The matched rule, or None if matching fails. + + Example: + >>> mp = Rule("(`p -> `q)\\n`p\\n`q\\n") + >>> pq = Rule("((! (! `x)) -> `x)") + >>> str(mp @ pq) + '(! (! `x))\\n----------\\n`x\\n' + """ capacity = buffer_size() rule = ds.Rule.match(self.value, other.value, capacity) if rule is None: diff --git a/pyds/search_t.py b/pyds/search_t.py index 4c6e15c..edad649 100644 --- a/pyds/search_t.py +++ b/pyds/search_t.py @@ -1,3 +1,5 @@ +"""Search engine for the deductive system.""" + __all__ = [ "Search", ] @@ -8,20 +10,70 @@ class Search: + """Search engine for the deductive system. + + Manages a knowledge base of rules and performs logical inference. + + Example: + >>> search = Search() + >>> search.add("(parent john mary)") + >>> search.add("(father `X `Y)\\n----------\\n(parent `X `Y)\\n") + >>> def callback(rule): + ... print(rule) + ... return False # Return False to continue, True to stop + >>> search.execute(callback) + """ + def __init__(self, limit_size: int = 1000, buffer_size: int = 10000): + """Creates a new search engine instance. + + Args: + limit_size: Size of the buffer for storing the final objects (rules/facts) + in the knowledge base (default: 1000). + buffer_size: Size of the buffer for internal operations like conversions + and transformations (default: 10000). + """ self._search: ds.Search = ds.Search(limit_size, buffer_size) def set_limit_size(self, limit_size: int) -> None: + """Set the size of the buffer for storing final objects. + + Args: + limit_size: The new limit size for storing rules/facts. + """ self._search.set_limit_size(limit_size) def set_buffer_size(self, buffer_size: int) -> None: + """Set the buffer size for internal operations. + + Args: + buffer_size: The new buffer size. + """ self._search.set_buffer_size(buffer_size) def reset(self) -> None: + """Reset the search engine, clearing all rules and facts.""" self._search.reset() def add(self, text: str) -> bool: + """Add a rule or fact to the knowledge base. + + Args: + text: The rule or fact as a string. + + Returns: + True if successfully added, False otherwise. + """ return self._search.add(text) def execute(self, callback: typing.Callable[[Rule], bool]) -> int: + """Execute the search engine with a callback for each inferred rule. + + Args: + callback: Function called for each candidate rule. Return False to continue, + True to stop. + + Returns: + The number of rules processed. + """ return self._search.execute(lambda candidate: callback(Rule(candidate.clone()))) diff --git a/pyds/string_t.py b/pyds/string_t.py index 2d89db2..d3541d6 100644 --- a/pyds/string_t.py +++ b/pyds/string_t.py @@ -1,3 +1,5 @@ +"""Wrapper class for deductive system strings.""" + __all__ = [ "String", ] @@ -7,4 +9,14 @@ class String(Common[ds.String]): + """Wrapper class for deductive system strings. + + Supports initialization from strings, buffers, or other instances. + + Example: + >>> str1 = String("hello") + >>> str2 = String(str1.data()) # From binary + >>> print(str1) # "hello" + """ + _base = ds.String diff --git a/pyds/term_t.py b/pyds/term_t.py index 806c36a..a944d36 100644 --- a/pyds/term_t.py +++ b/pyds/term_t.py @@ -1,3 +1,5 @@ +"""Wrapper class for logical terms in the deductive system.""" + from __future__ import annotations __all__ = [ @@ -13,10 +15,27 @@ class Term(Common[ds.Term]): + """Wrapper class for logical terms in the deductive system. + + A term can be a variable, item, or list. + + Example: + >>> term = Term("(f `x a)") + >>> inner_term = term.term # Get the underlying term type + """ + _base = ds.Term @property def term(self) -> Variable | Item | List: + """Extracts the underlying term and returns it as its concrete type. + + Returns: + The term as a Variable, Item, or List. + + Raises: + TypeError: If the term type is unexpected. + """ match self.value.get_type(): case ds.Term.Type.Variable: return Variable(self.value.variable()) @@ -31,6 +50,27 @@ def __floordiv__(self, other: Term) -> Term | None: return self.ground(other) def ground(self, other: Term, scope: str | None = None) -> Term | None: + """Ground this term using a dictionary to substitute variables with values. + + Args: + other: A term representing a dictionary (list of pairs). Each pair contains + a variable and its substitution value. + Example: Term("((`a b))") means substitute variable `a with value b. + scope: Optional scope string for variable scoping. + + Returns: + The grounded term, or None if grounding fails. + + Example: + >>> a = Term("`a") + >>> b = Term("((`a b))") + >>> str(a.ground(b)) # "b" + >>> + >>> # With scope + >>> c = Term("`a") + >>> d = Term("((x y `a `b) (y x `b `c))") + >>> str(c.ground(d, "x")) # "`c" + """ capacity = buffer_size() term = ds.Term.ground(self.value, other.value, scope, capacity) if term is None: diff --git a/pyds/variable_t.py b/pyds/variable_t.py index 3fb93cc..b9006ff 100644 --- a/pyds/variable_t.py +++ b/pyds/variable_t.py @@ -1,3 +1,5 @@ +"""Wrapper class for logical variables in the deductive system.""" + __all__ = [ "Variable", ] @@ -8,8 +10,22 @@ class Variable(Common[ds.Variable]): + """Wrapper class for logical variables in the deductive system. + + Variables are used in logical terms and can be unified. + + Example: + >>> var1 = Variable("`X") + >>> print(var1.name) # "X" + """ + _base = ds.Variable @property def name(self) -> String: + """Get the name of this variable. + + Returns: + The variable name as a String. + """ return String(self.value.name())