A practical guide to building neural-symbolic AI systems with TensorLogic
This guide will walk you through the TensorLogic ecosystem, from basic concepts to advanced applications, with hands-on examples at each step.
- What is TensorLogic?
- Installation
- Core Concepts
- Your First Logic-to-Tensor Program
- Working with the Ecosystem
- Advanced Topics
- Real-World Examples
- Troubleshooting
TensorLogic is a logic-as-tensor compilation framework that bridges symbolic reasoning and neural computation. It:
- Compiles logical rules (∀x. P(x) ∧ Q(x) → R(x)) into tensor operations (einsum graphs)
- Enables differentiable reasoning through automatic differentiation
- Integrates seamlessly with neural networks, probabilistic models, and knowledge graphs
- Provides multiple backends (SciRS2 CPU/SIMD, future GPU support)
✅ Perfect for:
- Combining symbolic knowledge with neural learning
- Enforcing logical constraints during training
- Probabilistic logic programming with PGMs
- Knowledge graph reasoning with tensor operations
- Neurosymbolic AI research and applications
❌ Not ideal for:
- Pure deep learning (use PyTorch/TensorFlow directly)
- Traditional logic programming (use Prolog/ASP)
- Simple rule-based systems (use production rule engines)
- Rust: 1.70+ (for Rust development)
- Python: 3.9+ (for Python bindings)
- SciRS2: Automatically included via workspace dependencies
# Clone the repository
git clone https://github.com/cool-japan/tensorlogic.git
cd tensorlogic
# Build all crates
cargo build --workspace --release
# Run tests to verify installation
cargo test --workspace --lib
# Expected output: 1,225+ tests passing# Install from source (development)
cd crates/tensorlogic-py
maturin develop --release
# Verify installation
python -c "import tensorlogic_py as tl; print(tl.__version__)"┌─────────────────────────────────────┐
│ Applications & Integration │
│ (OxiRS, SkleaRS, QuantrS2, etc.) │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Training & Inference │
│ (tensorlogic-train, -infer) │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Execution Backend │
│ (tensorlogic-scirs-backend) │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Compiler & Optimizer │
│ (tensorlogic-compiler) │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ IR & Core Types │
│ (tensorlogic-ir) │
└─────────────────────────────────────┘
TLExpr (Logical Expression):
- Represents logical formulas: predicates, quantifiers, connectives
- Example:
∀x. Bird(x) → CanFly(x)
EinsumGraph (Tensor Graph):
- Compiled tensor computation graph
- Nodes represent einsum operations
- Edges represent data flow
Executor:
- Runs EinsumGraphs on specific backends
- Supports forward pass (inference) and backward pass (training)
use tensorlogic_ir::{TLExpr, Term};
// Define variables
let x = Term::var("x");
let y = Term::var("y");
// Logical rule: friends(x, y) ∧ likes(y, z) → likes(x, z)
let friends = TLExpr::pred("friends", vec![x.clone(), y.clone()]);
let likes_yz = TLExpr::pred("likes", vec![y.clone(), Term::var("z")]);
let rule = TLExpr::imply(
TLExpr::and(friends, likes_yz),
TLExpr::pred("likes", vec![x, Term::var("z")])
);use tensorlogic_compiler::TlCompiler;
use tensorlogic_adapters::SymbolTable;
// Set up symbol table
let mut symbols = SymbolTable::new();
symbols.add_predicate("friends", vec!["Person", "Person"]);
symbols.add_predicate("likes", vec!["Person", "Thing"]);
symbols.add_domain("Person", 100); // 100 people
symbols.add_domain("Thing", 50); // 50 things
// Compile to einsum graph
let compiler = TlCompiler::new(symbols);
let graph = compiler.compile(&rule)?;
println!("Compiled to {} nodes", graph.nodes.len());use tensorlogic_scirs_backend::Scirs2Exec;
use tensorlogic_infer::TlExecutor;
use scirs2_core::ndarray::Array2;
use std::collections::HashMap;
// Create executor
let executor = Scirs2Exec::new();
// Prepare tensor data
let mut inputs = HashMap::new();
inputs.insert("friends".to_string(), Array2::zeros((100, 100)));
inputs.insert("likes".to_string(), Array2::zeros((100, 50)));
// Execute
let outputs = executor.execute(&graph, &inputs)?;
println!("Result shape: {:?}", outputs[0].shape());use tensorlogic_train::{
Trainer, TrainerConfig, AdamOptimizer, OptimizerConfig,
LogicalLoss, LossConfig, CrossEntropyLoss,
RuleSatisfactionLoss,
};
// Configure logical loss
let loss_config = LossConfig {
supervised_weight: 1.0,
rule_weight: 10.0, // Heavily enforce rules
..Default::default()
};
let loss = LogicalLoss::new(
loss_config,
Box::new(CrossEntropyLoss::default()),
vec![Box::new(RuleSatisfactionLoss::default())],
vec![],
);
// Configure optimizer with gradient clipping
let optimizer = AdamOptimizer::new(OptimizerConfig {
learning_rate: 0.001,
grad_clip: Some(5.0),
grad_clip_mode: GradClipMode::Norm,
..Default::default()
});
// Create trainer
let config = TrainerConfig {
num_epochs: 100,
batch_size: 32,
..Default::default()
};
let trainer = Trainer::new(config, Box::new(loss), Box::new(optimizer));
// Train with data
let history = trainer.train(
&train_data.view(),
&train_targets.view(),
Some(&val_data.view()),
Some(&val_targets.view()),
&mut parameters,
)?;use tensorlogic_quantrs_hooks::{
FactorGraph, Factor, SumProductAlgorithm, InferenceEngine,
};
use scirs2_core::ndarray::Array;
// Create factor graph
let mut graph = FactorGraph::new();
// Add variables
graph.add_variable_with_card("Rain".to_string(), "Boolean".to_string(), 2);
graph.add_variable_with_card("Sprinkler".to_string(), "Boolean".to_string(), 2);
// Add factors (CPTs)
let p_rain = Factor::new(
"P(Rain)".to_string(),
vec!["Rain".to_string()],
Array::from_shape_vec(vec![2], vec![0.3, 0.7]).unwrap().into_dyn()
).unwrap();
graph.add_factor(p_rain).unwrap();
// Run belief propagation
let algorithm = Box::new(SumProductAlgorithm::default());
let engine = InferenceEngine::new(graph, algorithm);
let marginals = engine.run()?;
println!("P(Rain) = {:?}", marginals.get("Rain"));use tensorlogic_oxirs_bridge::{RdfToTlConverter, ShaclValidator};
// Load RDF knowledge graph
let rdf_data = r#"
@prefix : <http://example.org/> .
:Alice :knows :Bob .
:Bob :knows :Charlie .
"#;
// Convert RDF to TensorLogic expressions
let converter = RdfToTlConverter::new();
let tl_exprs = converter.convert_rdf_to_tl(rdf_data)?;
// Validate with SHACL shapes
let shacl_shape = r#"
@prefix sh: <http://www.w3.org/ns/shacl#> .
:PersonShape a sh:NodeShape ;
sh:targetClass :Person ;
sh:property [
sh:path :knows ;
sh:minCount 1 ;
] .
"#;
let validator = ShaclValidator::new();
let violations = validator.validate(rdf_data, shacl_shape)?;use tensorlogic_train::{Loss, TrainResult};
use scirs2_core::ndarray::{Array, ArrayView, Ix2};
#[derive(Debug, Clone)]
pub struct CustomConstraintLoss {
pub penalty_weight: f64,
}
impl Loss for CustomConstraintLoss {
fn compute(
&self,
predictions: &ArrayView<f64, Ix2>,
targets: &ArrayView<f64, Ix2>,
) -> TrainResult<f64> {
// Implement custom logic
let mut total = 0.0;
for i in 0..predictions.nrows() {
for j in 0..predictions.ncols() {
let violation = (predictions[[i, j]] - targets[[i, j]]).abs();
if violation > 0.1 {
total += self.penalty_weight * violation.powi(2);
}
}
}
Ok(total / predictions.nrows() as f64)
}
fn gradient(
&self,
predictions: &ArrayView<f64, Ix2>,
targets: &ArrayView<f64, Ix2>,
) -> TrainResult<Array<f64, Ix2>> {
let mut grad = Array::zeros(predictions.raw_dim());
// Compute gradients...
Ok(grad)
}
}use tensorlogic_infer::{TlStreamingExecutor, StreamingConfig, StreamingMode};
let config = StreamingConfig::new(StreamingMode::Adaptive {
initial_chunk: 64,
})
.with_prefetch(2)
.with_checkpointing(100);
let results = executor.execute_stream(&graph, input_stream, &config)?;
for result in results {
println!("Processed chunk {} in {}ms",
result.metadata.chunk_id,
result.processing_time_ms
);
}use tensorlogic_infer::{PlacementOptimizer, PlacementStrategy, Device};
let devices = vec![Device::CPU(0), Device::GPU(0)];
let optimizer = PlacementOptimizer::new(devices, PlacementStrategy::LoadBalance);
let plan = optimizer.optimize(&graph)?;
println!("Placement plan:");
for (node_id, device) in &plan.node_placements {
println!(" Node {} → {:?}", node_id, device);
}use tensorlogic_train::{
L2Regularization, Regularizer,
NoiseAugmenter, MixupAugmenter, CompositeAugmenter, DataAugmenter,
};
// Regularization
let regularizer = L2Regularization::new(0.01);
let penalty = regularizer.compute_penalty(¶meters)?;
// Data augmentation pipeline
let mut augmenter = CompositeAugmenter::new();
augmenter.add(Box::new(NoiseAugmenter::new(0.0, 0.05)));
augmenter.add(Box::new(MixupAugmenter::new(1.0)));
let augmented = augmenter.augment(&data.view())?;Problem: Predict missing relationships in a knowledge graph.
use tensorlogic_ir::TLExpr;
use tensorlogic_compiler::TlCompiler;
use tensorlogic_train::{Trainer, TrainerConfig, ContrastiveLoss};
// Rule: Similar entities have similar relationships
// ∀x,y,z. Similar(x,y) ∧ HasRel(y,z) → HasRel(x,z)
let similar = TLExpr::pred("Similar", vec![Term::var("x"), Term::var("y")]);
let has_rel_yz = TLExpr::pred("HasRel", vec![Term::var("y"), Term::var("z")]);
let has_rel_xz = TLExpr::pred("HasRel", vec![Term::var("x"), Term::var("z")]);
let rule = TLExpr::imply(
TLExpr::and(similar, has_rel_yz),
has_rel_xz
);
// Compile and train with contrastive loss for embeddings
let graph = compile(&rule)?;
let loss = ContrastiveLoss::new(1.0);
// Train to learn embeddings that satisfy the rule
// ...Problem: Classify images with logical constraints (e.g., "if it has wings, it's likely a bird").
use tensorlogic_train::{LogicalLoss, ConstraintViolationLoss};
// Define constraint: HasWings(x) → IsBird(x) with high confidence
let has_wings = TLExpr::pred("HasWings", vec![Term::var("x")]);
let is_bird = TLExpr::pred("IsBird", vec![Term::var("x")]);
let constraint = TLExpr::imply(has_wings, is_bird);
// Use logical loss to enforce constraint during training
let logical_loss = LogicalLoss::new(
LossConfig {
supervised_weight: 1.0,
constraint_weight: 5.0, // High penalty for violations
..Default::default()
},
Box::new(CrossEntropyLoss::default()),
vec![],
vec![Box::new(ConstraintViolationLoss::default())],
);
// Train neural network with constraint enforcement
// ...Problem: Answer queries over uncertain knowledge using probabilistic inference.
use tensorlogic_quantrs_hooks::{FactorGraph, SumProductAlgorithm};
// Knowledge: "Bob is friends with Alice (0.9 probability)"
// "Alice likes pizza (0.8 probability)"
// "Friends usually share food preferences (0.7)"
// Build factor graph
let mut graph = FactorGraph::new();
// ... add variables and factors ...
// Query: P(Bob likes pizza | evidence)
let algorithm = SumProductAlgorithm::new(100, 1e-6, 0.0);
let engine = InferenceEngine::new(graph, Box::new(algorithm));
let marginal = engine.marginalize(&MarginalizationQuery {
variable: "BobLikesPizza".to_string(),
})?;
println!("P(Bob likes pizza) = {:.3}", marginal[[1]]);Problem: Domain mismatches in predicates.
// ❌ Wrong: Inconsistent domains
symbols.add_predicate("knows", vec!["Person", "Thing"]);
let expr = TLExpr::pred("knows", vec![person_var, person_var2]);
// ✅ Correct: Matching domains
symbols.add_predicate("knows", vec!["Person", "Person"]);Problem: Input tensors don't match expected dimensions.
// Check expected shapes from symbol table
println!("Expected shape for 'knows': ({}, {})",
symbols.get_domain_size("Person")?,
symbols.get_domain_size("Person")?
);
// Ensure input tensors match
let knows_tensor = Array2::zeros((
symbols.get_domain_size("Person")?,
symbols.get_domain_size("Person")?
));Solutions:
// 1. Enable SIMD backend
cargo build --features simd
// 2. Use batch execution
let results = executor.execute_batch_parallel(&graph, batch_inputs, Some(4))?;
// 3. Enable memory pooling
let mut pool = MemoryPool::new();
// ... executor uses pool for allocations
// 4. Use streaming for large data
let config = StreamingConfig::new(StreamingMode::DynamicChunk {
target_memory_mb: 512,
});Debugging steps:
// 1. Check learning rate
let lr_finder = LearningRateFinder::new(1e-7, 10.0, 100, true);
// ... run LR finder to find optimal LR
// 2. Monitor gradients
let grad_monitor = GradientMonitor::new(1e-5, 100.0);
callbacks.add(Box::new(grad_monitor));
// Check for vanishing/exploding gradients
// 3. Reduce constraint weight
let loss_config = LossConfig {
supervised_weight: 1.0,
constraint_weight: 0.1, // Start small, increase gradually
..Default::default()
};
// 4. Use gradient clipping
let optimizer = AdamOptimizer::new(OptimizerConfig {
grad_clip: Some(1.0),
grad_clip_mode: GradClipMode::Norm,
..Default::default()
});- Documentation: Check crate-level README files in
crates/*/README.md - Examples: Browse
crates/*/examples/for working code - Tests: Look at
crates/*/src/**/*_tests.rsfor usage patterns - Issues: Report bugs at https://github.com/cool-japan/tensorlogic/issues
- Discussions: Ask questions in GitHub Discussions
- ✅ Complete this guide
- ⏭️ Run examples in
crates/tensorlogic-compiler/examples/ - ⏭️ Build a simple knowledge graph completion system
- ⏭️ Explore training with constraints using
tensorlogic-train
- ✅ Master basic compilation and execution
- ⏭️ Study
tensorlogic-quantrs-hooksfor probabilistic reasoning - ⏭️ Implement custom loss functions
- ⏭️ Integrate with existing neural architectures
- ✅ Understand the full compilation pipeline
- ⏭️ Contribute custom optimization passes
- ⏭️ Implement domain-specific backends
- ⏭️ Research novel neurosymbolic architectures
- Architecture Overview: System design and internals
- Loss Function Guide: Choosing the right loss
- Hyperparameter Tuning: Optimization strategies
- SciRS2 Integration Policy: Backend integration guidelines
// Import commonly used items
use tensorlogic_ir::{TLExpr, Term};
use tensorlogic_compiler::TlCompiler;
use tensorlogic_scirs_backend::Scirs2Exec;
use tensorlogic_infer::TlExecutor;
use tensorlogic_train::{Trainer, TrainerConfig, AdamOptimizer};
// Build logical expression
let expr = TLExpr::and(
TLExpr::pred("P", vec![Term::var("x")]),
TLExpr::pred("Q", vec![Term::var("x")])
);
// Compile
let graph = compiler.compile(&expr)?;
// Execute
let outputs = executor.execute(&graph, &inputs)?;
// Train
let history = trainer.train(&data, &targets, None, None, &mut params)?;// Quantifiers
TLExpr::exists("x", "Domain", body)
TLExpr::forall("x", "Domain", body)
// Connectives
TLExpr::and(left, right)
TLExpr::or(left, right)
TLExpr::not(expr)
TLExpr::imply(premise, conclusion)
// Arithmetic
TLExpr::add(left, right)
TLExpr::mul(left, right)
// Aggregation
TLExpr::aggregate(AggregateOp::Sum, "x", "Domain", body, vec![])Happy Logic-as-Tensor Programming! 🚀
For more examples and detailed documentation, explore the crates/ directory and individual crate READMEs.
Version: 0.1.0-rc.1 Status: Production Ready