tensor-theorem-prover

First-order logic theorem prover supporting unification with approximate vector similarity

https://github.com/chanind/tensor-theorem-prover

Science Score: 54.0%

This score indicates how likely this project is to be science-related based on various indicators:

  • CITATION.cff file
    Found CITATION.cff file
  • codemeta.json file
    Found codemeta.json file
  • .zenodo.json file
    Found .zenodo.json file
  • DOI references
  • Academic publication links
    Links to: arxiv.org
  • Committers with academic emails
  • Institutional organization owner
  • JOSS paper metadata
  • Scientific vocabulary similarity
    Low similarity (12.3%) to scientific vocabulary

Keywords

first-order-logic logic reasoning symbolic-logic theorem-proving

Keywords from Contributors

spacy-extension transformers huggingface huggingface-transformers abstract-meaning-representation amr framenet semantic-parsing t5 jax
Last synced: 6 months ago · JSON representation ·

Repository

First-order logic theorem prover supporting unification with approximate vector similarity

Basic Info
Statistics
  • Stars: 12
  • Watchers: 2
  • Forks: 1
  • Open Issues: 0
  • Releases: 30
Topics
first-order-logic logic reasoning symbolic-logic theorem-proving
Created over 3 years ago · Last pushed almost 3 years ago
Metadata Files
Readme Changelog Contributing License Citation

README.md

Tensor Theorem Prover

ci Codecov PyPI Documentation Status

First-order logic theorem prover supporting unification with approximate vector similarity

Full docs: https://tensor-theorem-prover.readthedocs.io

Installation

pip install tensor-theorem-prover

Usage

tensor-theorem-prover can be used either as a standard symbolic first-order theorem prover, or it can be used with vector embeddings and fuzzy unification.

The basic setup requires listing out first-order formulae, and using the ResolutionProver class to generate proofs.

```python import numpy as np from vectortheoremprover import ResolutionProver, Constant, Predicate, Variable, Implies

X = Variable("X") Y = Variable("Y") Z = Variable("Z")

predicates and constants can be given an embedding array for fuzzy unification

grandpaof = Predicate("grandpaof", np.array([1.0, 1.0, 0.0, 0.3, ...])) grandfatherof = Predicate("grandfatherof", np.array([1.01, 0.95, 0.05, 0.33, ...])) parentof = Predicate("parentof", np.array([ ... ])) fatherof = Predicate("fatherof", np.array([ ... ])) bart = Constant("bart", np.array([ ... ])) homer = Constant("homer", np.array([ ... ])) abe = Constant("abe", np.array([ ... ]))

knowledge = [ parentof(homer, bart), fatherof(abe, homer), # fatherof(X, Z) ^ parentof(Z, Y) -> grandpaof(X, Y) Implies(And(fatherof(X, Z), parentof(Z, Y)), grandpaof(X, Y)) ]

prover = ResolutionProver(knowledge=knowledge)

query the prover to find who is bart's grandfather

proof = prover.prove(grandfather_of(X, bart))

even though grandpa_of and grandfather_of are not identical symbols,

their embedding is close enough that the prover can still find the answer

print(proof.substitutions[X]) # abe

the prover will return None if a proof could not be found

failedproof = prover.prove(grandfatherof(bart, homer)) print(failed_proof) # None

```

Working with proof results

The prover.prove() method will return a Proof object if a successful proof is found. This object contains a list of all the resolutions, substitutions, and similarity calculations that went into proving the goal.

```python proof = prover.prove(goal)

proof.substitutions # a map of all variables in the goal to their bound values proof.similarity # the min similarity of all unify operations in this proof proof.depth # the number of steps in this proof proof.proof_steps # all the proof steps involved, including all resolutions and unifications along the way ```

The Proof object can be printed as a string to get a visual overview of the steps involved in the proof.

```python X = Variable("X") Y = Variable("Y") fatherof = Predicate("fatherof") parentof = Predicate("parentof") ismale = Predicate("ismale") bart = Constant("bart") homer = Constant("homer")

knowledge = [ parentof(homer, bart), ismale(homer), Implies(And(parentof(X, Y), ismale(X)), father_of(X, Y)), ]

prover = ResolutionProver(knowledge=knowledge)

goal = father_of(homer, X) proof = prover.prove(goal)

print(proof)

Goal: [¬father_of(homer,X)]

Subsitutions: {X -> bart}

Similarity: 1.0

Depth: 3

Steps:

Similarity: 1.0

Source: [¬father_of(homer,X)]

Target: [fatherof(X,Y) ∨ ¬ismale(X) ∨ ¬parent_of(X,Y)]

Unify: fatherof(homer,X) = fatherof(X,Y)

Subsitutions: {}, {X -> homer, Y -> X}

Resolvent: [¬ismale(homer) ∨ ¬parentof(homer,X)]

---

Similarity: 1.0

Source: [¬ismale(homer) ∨ ¬parentof(homer,X)]

Target: [parent_of(homer,bart)]

Unify: parentof(homer,X) = parentof(homer,bart)

Subsitutions: {X -> bart}, {}

Resolvent: [¬is_male(homer)]

---

Similarity: 1.0

Source: [¬is_male(homer)]

Target: [is_male(homer)]

Unify: ismale(homer) = ismale(homer)

Subsitutions: {}, {}

Resolvent: []

```

Finding all possible proofs

The prover.prove() method will return the proof with the highest similarity score among all possible proofs, if one exists. If you want to get a list of all the possible proofs in descending order of similarity score, you can call prover.prove_all() to return a list of all proofs.

Custom matching functions and similarity thresholds

By default, the prover will use cosine similarity for unification. If you'd like to use a different similarity function, you can pass in a function to the prover to perform the similarity calculation however you wish.

```python def fancy_similarity(item1, item2): norm = np.linalg.norm(item1.embedding) + np.linalg.norm(item2.embedding) return np.linalg.norm(item1.embedding - item2.embedding) / norm

prover = ResolutionProver(knowledge=knowledge, similarityfunc=fancysimilarity) ```

By default, there is a minimum similarity threshold of 0.5 for a unification to success. You can customize this as well when creating a ResolutionProver instance

python prover = ResolutionProver(knowledge=knowledge, min_similarity_threshold=0.9)

Working with Tensors (Pytorch, Tensorflow, etc...)

By default, the similarity calculation assumes that the embeddings supplied for constants and predicates are numpy arrays. If you want to use tensors instead, this will work as long as you provide a similarity_func which can work with the tensor types you're using and return a float.

For example, if you're using Pytorch, it might look like the following:

```python import torch

def torchcosinesimilarity(item1, item2): similarity = torch.nn.functional.cosine_similarity( item1.embedding, item2.embedding, 0 ) return similarity.item()

prover = ResolutionProver(knowledge=knowledge, similarityfunc=torchcosine_similarity)

for pytorch you may want to wrap the proving in torch.no_grad()

with torch.no_grad(): proof = prover.prove(goal) ```

Max proof depth

By default, the ResolutionProver will abort proofs after a depth of 10. You can customize this behavior by passing max_proof_depth when creating the prover

python prover = ResolutionProver(knowledge=knowledge, max_proof_depth=10)

Max resolvent width

By default, the ResolutionProver has no limit on how wide resolvents can get during the proving process. If the proofs are running too slowly, you can try to set max_resolvent_width to limit how many literals intermediate resolvents are allowed to contain. This should narrow the search tree, but has the trade-off of not finding proofs if the proof requires unifying together a lot of very wide clauses.

python prover = ResolutionProver(knowledge=knowledge, max_resolvent_width=10)

Skipping seen resolvents

A major performance improvement when searching through a large proof space is to stop searching any branches that encounter a resolvent that's already been seen. Doing this is still guaranteed to find the proof with the highest similarity score, but it means the prover is no longer guaranteed to find every possible proof when running prover.prove_all(). Although, when dealing with anything beyond very small knowledge bases, finding every possible proof is likely not going to be computationally feasible anyway.

Searching for a proof using prover.prove() always enables this optimization, but you can enable it when using prover.prove_all() as well by passing the option skip_seen_resolvents=True when creating the ResolutionProver, like below:

python prover = ResolutionProver(knowledge=knowledge, skip_seen_resolvents=True)

Max resolution attempts

As a final backstop against the search tree getting too large, you can set a maximum resolution attempts parameter to force the prover to give up after a finite amount of attempts. You can set this parameter when creating a ResolutionProver as shown below:

python prover = ResolutionProver(knowledge=knowledge, max_resolution_attempts=100_000_000)

Multithreading

By default, the ResolutionProver will try to use available CPU cores up to a max of 6, though this may change in future releases. If you want to explicitly control the number of worker threads used for solving, pass num_workers when creating the ResolutionProver, like below:

python prover = ResolutionProver(knowledge=knowledge, num_workers=1)

Acknowledgements

This library borrows code and ideas from the earier library fuzzy-reasoner. The main difference between these libraries is that tensor-theorem-prover supports full first-order logic using Resolution, whereas fuzzy-reasoner is restricted to Horn clauses and uses backwards chaining. This library is also much more optimized than the fuzzy-reasoner, as the core of tensor-theorem-prover is written in rust and supports multithreading, while fuzzy-reasoner is pure Python.

Like fuzzy-reasoner, this library also takes inspiration from the following papers for the idea of using vector similarity in theorem proving:

Contributing

Contributions are welcome! Please leave an issue in the Github repo if you find any bugs, and open a pull request with and fixes or improvements that you'd like to contribute.

Citation

If you use tensor-theorem-prover in your work, please cite the following:

bibtex @article{chanin2023neuro, title={Neuro-symbolic Commonsense Social Reasoning}, author={Chanin, David and Hunter, Anthony}, journal={arXiv preprint arXiv:2303.08264}, year={2023} }

Owner

  • Name: David Chanin
  • Login: chanind
  • Kind: user
  • Location: London, UK
  • Company: UCL

Citation (CITATION.cff)

cff-version: 1.2.0
message: "If you use this software, please cite it as below."
authors:
- family-names: "Chanin"
  given-names: "David"
- family-names: "Hunter"
  given-names: "Anthony"
title: "Neuro-symbolic Commonsense Social Reasoning"
doi: 10.48550/arXiv.2303.08264
date-released: 2023-03-14
url: "https://arxiv.org/abs/2303.08264"

GitHub Events

Total
  • Watch event: 4
Last Year
  • Watch event: 4

Committers

Last synced: 8 months ago

All Time
  • Total Commits: 121
  • Total Committers: 3
  • Avg Commits per committer: 40.333
  • Development Distribution Score (DDS): 0.264
Past Year
  • Commits: 0
  • Committers: 0
  • Avg Commits per committer: 0.0
  • Development Distribution Score (DDS): 0.0
Top Committers
Name Email Commits
David Chanin c****v@g****m 89
github-actions a****n@g****m 31
github-actions g****s@g****m 1
Committer Domains (Top 20 + Academic)

Issues and Pull Requests

Last synced: 7 months ago

All Time
  • Total issues: 0
  • Total pull requests: 6
  • Average time to close issues: N/A
  • Average time to close pull requests: about 1 hour
  • Total issue authors: 0
  • Total pull request authors: 1
  • Average comments per issue: 0
  • Average comments per pull request: 0.67
  • Merged pull requests: 6
  • Bot issues: 0
  • Bot pull requests: 0
Past Year
  • Issues: 0
  • Pull requests: 0
  • Average time to close issues: N/A
  • Average time to close pull requests: N/A
  • Issue authors: 0
  • Pull request authors: 0
  • Average comments per issue: 0
  • Average comments per pull request: 0
  • Merged pull requests: 0
  • Bot issues: 0
  • Bot pull requests: 0
Top Authors
Issue Authors
Pull Request Authors
  • chanind (6)
Top Labels
Issue Labels
Pull Request Labels

Packages

  • Total packages: 1
  • Total downloads:
    • pypi 218 last-month
  • Total dependent packages: 0
  • Total dependent repositories: 0
  • Total versions: 26
  • Total maintainers: 1
pypi.org: tensor-theorem-prover

Customizable first-order logic theorem prover supporting approximate vector similarity in unification

  • Versions: 26
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Downloads: 218 Last month
Rankings
Dependent packages count: 6.6%
Downloads: 8.3%
Average: 19.9%
Stargazers count: 23.3%
Forks count: 30.5%
Dependent repos count: 30.6%
Maintainers (1)
Last synced: 6 months ago

Dependencies

.github/workflows/ci.yaml actions
  • PyO3/maturin-action v1 composite
  • actions/checkout v3 composite
  • actions/download-artifact v3 composite
  • actions/setup-python v3 composite
  • actions/setup-python v4 composite
  • actions/upload-artifact v3 composite
  • addnab/docker-run-action v3 composite
  • codecov/codecov-action v2 composite
  • dtolnay/rust-toolchain stable composite
  • relekang/python-semantic-release v7.33.1 composite
  • snok/install-poetry v1 composite
pyproject.toml pypi
  • black ^22.10.0 develop
  • flake8 ^5.0.4 develop
  • mypy ^0.982 develop
  • pytest ^7.1.3 develop
  • pytest-cov ^4.0.0 develop
  • syrupy ^3.0.2 develop
  • numpy ^1.20.0
  • python >=3.7, <4.0
Cargo.toml cargo