A deductive system for logical inference, implemented in C++. The library provides bindings for Python (via pybind11) and TypeScript/JavaScript (via Emscripten/WebAssembly).
- C++ Core: The core implementation in
src/andinclude/ds/provides the fundamental data structures and algorithms - Python Bindings: Built with pybind11, wrapping the C++ core (see
apyds/) - TypeScript/JavaScript Bindings: Built with Emscripten, compiling C++ to WebAssembly (see
atsds/)
- Multi-Language Support: Use the same deductive system in C++, Python, or TypeScript/JavaScript
- Logical Terms: Work with variables, items (constants/functors), and lists
- Rule-Based Inference: Define rules and facts, perform logical deduction
- Unification and Matching: Unify terms and match rules
- Search Engine: Built-in search mechanism for iterative inference
- WebAssembly: Run inference in the browser or Node.js environments
- Type-Safe: Strong typing support in TypeScript and Python
The TypeScript/JavaScript package wraps the C++ core via WebAssembly.
npm install atsdsThe package includes WebAssembly binaries and TypeScript type definitions.
The Python package wraps the C++ core via pybind11.
pip install apydsRequires Python 3.10-3.14.
The C++ library is the core implementation. Both Python and TypeScript bindings are built on top of it.
Clone the repository and use the overlay port:
git clone https://github.com/USTC-KnowledgeComputingLab/ds.git
vcpkg install ds --overlay-ports=./ds/portsAdd to your vcpkg.json:
{
"dependencies": ["ds"]
}In your CMakeLists.txt:
find_package(ds CONFIG REQUIRED)
target_link_libraries(your_target PRIVATE ds::ds)git clone https://github.com/USTC-KnowledgeComputingLab/ds.git
cd ds
cmake -B build
cmake --build buildInclude the headers from include/ds/ in your C++ project.
import { Rule, Search } from "atsds";
// Create a search engine
const search = new Search(1000, 10000);
// Modus ponens: P -> Q, P |- Q
search.add("(`P -> `Q) `P `Q");
// Axiom schema 1: p -> (q -> p)
search.add("(`p -> (`q -> `p))");
// Axiom schema 2: (p -> (q -> r)) -> ((p -> q) -> (p -> r))
search.add("((`p -> (`q -> `r)) -> ((`p -> `q) -> (`p -> `r)))");
// Axiom schema 3: (!p -> !q) -> (q -> p)
search.add("(((! `p) -> (! `q)) -> (`q -> `p))");
// Premise: !!X
search.add("(! (! X))");
// Target: X (double negation elimination)
const target = new Rule("X");
// Execute search until target is found
while (true) {
let found = false;
search.execute((candidate) => {
if (candidate.key() === target.key()) {
console.log("Found:", candidate.toString());
found = true;
return true; // Stop search
}
return false; // Continue searching
});
if (found) break;
}import apyds
# Create a search engine
search = apyds.Search(1000, 10000)
# Modus ponens: P -> Q, P |- Q
search.add("(`P -> `Q) `P `Q")
# Axiom schema 1: p -> (q -> p)
search.add("(`p -> (`q -> `p))")
# Axiom schema 2: (p -> (q -> r)) -> ((p -> q) -> (p -> r))
search.add("((`p -> (`q -> `r)) -> ((`p -> `q) -> (`p -> `r)))")
# Axiom schema 3: (!p -> !q) -> (q -> p)
search.add("(((! `p) -> (! `q)) -> (`q -> `p))")
# Premise: !!X
search.add("(! (! X))")
# Target: X (double negation elimination)
target = apyds.Rule("X")
# Execute search until target is found
while True:
found = False
def callback(candidate):
global found
if candidate == target:
print("Found:", candidate)
found = True
return True # Stop search
return False # Continue searching
search.execute(callback)
if found:
break#include <cstdio>
#include <cstring>
#include <ds/ds.hh>
#include <ds/search.hh>
#include <ds/utility.hh>
int main() {
ds::search_t search(1000, 10000);
// Modus ponens: P -> Q, P |- Q
search.add("(`P -> `Q) `P `Q");
// Axiom schema 1: p -> (q -> p)
search.add("(`p -> (`q -> `p))");
// Axiom schema 2: (p -> (q -> r)) -> ((p -> q) -> (p -> r))
search.add("((`p -> (`q -> `r)) -> ((`p -> `q) -> (`p -> `r)))");
// Axiom schema 3: (!p -> !q) -> (q -> p)
search.add("(((! `p) -> (! `q)) -> (`q -> `p))");
// Premise: !!X
search.add("(! (! X))");
// Target: X (double negation elimination)
auto target = ds::text_to_rule("X", 1000);
// Execute search until target is found
while (true) {
bool found = false;
search.execute([&](ds::rule_t* candidate) {
if (candidate->data_size() == target->data_size() &&
memcmp(candidate->head(), target->head(), candidate->data_size()) == 0) {
printf("Found: %s", ds::rule_to_text(candidate, 1000).get());
found = true;
return true; // Stop search
}
return false; // Continue searching
});
if (found) break;
}
return 0;
}Terms are the basic building blocks of the deductive system:
- Variables: Prefixed with backtick, e.g.,
`X,`P,`Q - Items: Constants or functors, e.g.,
a,father,! - Lists: Ordered sequences enclosed in parentheses, e.g.,
(a b c),(father john mary)
Rules consist of zero or more premises (above the line) and a conclusion (below the line):
premise1
premise2
----------
conclusion
A fact is a rule without premises:
----------
(parent john mary)
Grounding substitutes variables with values using a dictionary:
const a = new Term("`a");
const dict = new Term("((`a b))"); // Substitute `a with b
const result = a.ground(dict);
console.log(result.toString()); // "b"Matching unifies the first premise of a rule with a fact to produce a new rule. For example, applying modus ponens to double negation elimination:
// Modus ponens rule: (p -> q), p |- q
const mp = new Rule("(`p -> `q)\n`p\n`q\n");
// Double negation elimination axiom: !!x -> x
const pq = new Rule("((! (! `x)) -> `x)");
// Match produces: !!x |- x
console.log(mp.match(pq).toString()); // "(! (! `x))\n----------\n`x\n"buffer_size(size?: number): Get/set buffer size for internal operationsString_: String wrapper classVariable: Logical variable classItem: Item (constant/functor) classList: List classTerm: General term class (variable, item, or list)Rule: Logical rule classSearch: Search engine for inference
buffer_size(size: int): Set buffer sizescoped_buffer_size(size: int): Context manager for temporary buffer sizeString: String wrapper classVariable: Logical variable classItem: Item (constant/functor) classList: List classTerm: General term classRule: Logical rule classSearch: Search engine for inference
All classes are in the ds namespace:
string_t: String handlingvariable_t: Logical variablesitem_t: Items (constants/functors)list_t: Liststerm_t: General termsrule_t: Logical rulessearch_t: Search engine (in<ds/search.hh>)
See header files in include/ds/ for detailed API documentation.
- For TypeScript: Emscripten SDK
- For Python: Python 3.10-3.14, C++20 compatible compiler, CMake 3.30+
- For C++: C++20 compatible compiler, CMake 3.30+
# Clone repository
git clone https://github.com/USTC-KnowledgeComputingLab/ds.git
cd ds
# Build TypeScript/JavaScript (requires Emscripten)
npm install
npm run build
# Build Python package
uv sync --extra dev
# Build C++ library
cmake -B build
cmake --build build# TypeScript/JavaScript tests
npm test
# Python tests
pytest
# C++ tests (if available)
cd build && ctestExample programs are provided in the examples/ directory:
examples/main.mjs: TypeScript/JavaScript exampleexamples/main.py: Python exampleexamples/main.cc: C++ example
Each example demonstrates logical inference using propositional logic axioms.
- BNF Conversion Library (apyds-bnf, atsds-bnf): Bidirectional conversion between DS syntax formats. See /bnf for details.
The project uses code formatting tools for consistency:
- C++: clang-format (
.clang-formatconfig provided) - Python: ruff (configured in
pyproject.toml) - TypeScript: Biome (configured in
biome.json)
Pre-commit hooks are configured in .pre-commit-config.yaml.
This project is licensed under the GNU General Public License v3.0 or later. See LICENSE.md for details.
For comprehensive documentation including tutorials, API reference, and examples, visit:
- GitHub: USTC-KnowledgeComputingLab/ds
- Documentation: ustc-knowledgecomputinglab.github.io/ds
- npm package: atsds
- PyPI package: apyds
Hao Zhang [email protected]
Contributions are welcome! Please feel free to submit issues or pull requests.