Skip to main content

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

Project description

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.

import numpy as np
from vector_theorem_prover 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
grandpa_of = Predicate("grandpa_of", np.array([1.0, 1.0, 0.0, 0.3, ...]))
grandfather_of = Predicate("grandfather_of", np.array([1.01, 0.95, 0.05, 0.33, ...]))
parent_of = Predicate("parent_of", np.array([ ... ]))
father_of = Predicate("father_of", np.array([ ... ]))
bart = Constant("bart", np.array([ ... ]))
homer = Constant("homer", np.array([ ... ]))
abe = Constant("abe", np.array([ ... ]))

knowledge = [
    parent_of(homer, bart),
    father_of(abe, homer),
    # father_of(X, Z) ^ parent_of(Z, Y) -> grandpa_of(X, Y)
    Implies(And(father_of(X, Z), parent_of(Z, Y)), grandpa_of(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
failed_proof = prover.prove(grandfather_of(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.

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.

X = Variable("X")
Y = Variable("Y")
father_of = Predicate("father_of")
parent_of = Predicate("parent_of")
is_male = Predicate("is_male")
bart = Constant("bart")
homer = Constant("homer")

knowledge = [
    parent_of(homer, bart),
    is_male(homer),
    Implies(And(parent_of(X, Y), is_male(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: [father_of(X,Y) ∨ ¬is_male(X) ∨ ¬parent_of(X,Y)]
#   Unify: father_of(homer,X) = father_of(X,Y)
#   Subsitutions: {}, {X -> homer, Y -> X}
#   Resolvent: [¬is_male(homer) ∨ ¬parent_of(homer,X)]
#   ---
#   Similarity: 1.0
#   Source: [¬is_male(homer) ∨ ¬parent_of(homer,X)]
#   Target: [parent_of(homer,bart)]
#   Unify: parent_of(homer,X) = parent_of(homer,bart)
#   Subsitutions: {X -> bart}, {}
#   Resolvent: [¬is_male(homer)]
#   ---
#   Similarity: 1.0
#   Source: [¬is_male(homer)]
#   Target: [is_male(homer)]
#   Unify: is_male(homer) = is_male(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.

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, similarity_func=fancy_similarity)

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

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:

import torch

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

prover = ResolutionProver(knowledge=knowledge, similarity_func=torch_cosine_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

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.

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:

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:

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:

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tensor_theorem_prover-0.14.0.tar.gz (3.1 MB view details)

Uploaded Source

Built Distributions

tensor_theorem_prover-0.14.0-cp38-none-win_amd64.whl (694.1 kB view details)

Uploaded CPython 3.8 Windows x86-64

tensor_theorem_prover-0.14.0-cp38-none-win32.whl (626.3 kB view details)

Uploaded CPython 3.8 Windows x86

tensor_theorem_prover-0.14.0-cp38-cp38-musllinux_1_2_x86_64.whl (2.0 MB view details)

Uploaded CPython 3.8 musllinux: musl 1.2+ x86-64

tensor_theorem_prover-0.14.0-cp38-cp38-musllinux_1_2_i686.whl (2.0 MB view details)

Uploaded CPython 3.8 musllinux: musl 1.2+ i686

tensor_theorem_prover-0.14.0-cp38-cp38-musllinux_1_2_armv7l.whl (2.0 MB view details)

Uploaded CPython 3.8 musllinux: musl 1.2+ ARMv7l

tensor_theorem_prover-0.14.0-cp38-cp38-musllinux_1_2_aarch64.whl (1.9 MB view details)

Uploaded CPython 3.8 musllinux: musl 1.2+ ARM64

tensor_theorem_prover-0.14.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.8 MB view details)

Uploaded CPython 3.8 manylinux: glibc 2.17+ x86-64

tensor_theorem_prover-0.14.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl (1.8 MB view details)

Uploaded CPython 3.8 manylinux: glibc 2.12+ i686

tensor_theorem_prover-0.14.0-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl (1.7 MB view details)

Uploaded CPython 3.8 macOS 10.9+ universal2 (ARM64, x86-64) macOS 10.9+ x86-64 macOS 11.0+ ARM64

tensor_theorem_prover-0.14.0-cp38-cp38-macosx_10_7_x86_64.whl (902.7 kB view details)

Uploaded CPython 3.8 macOS 10.7+ x86-64

File details

Details for the file tensor_theorem_prover-0.14.0.tar.gz.

File metadata

  • Download URL: tensor_theorem_prover-0.14.0.tar.gz
  • Upload date:
  • Size: 3.1 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.3.2 CPython/3.10.6 Linux/5.15.0-1031-azure

File hashes

Hashes for tensor_theorem_prover-0.14.0.tar.gz
Algorithm Hash digest
SHA256 aa5e121c235ac3d6b56961ff92721f8f5c25f7a8a9c0b8539f3e448a7f32d4a7
MD5 6870b72db0f706230264af9c720ecc39
BLAKE2b-256 cec016f418092bf56bf0fcbe1cdc4b30e7fca6dea77c2dc7f487da320941b3d5

See more details on using hashes here.

File details

Details for the file tensor_theorem_prover-0.14.0-cp38-none-win_amd64.whl.

File metadata

File hashes

Hashes for tensor_theorem_prover-0.14.0-cp38-none-win_amd64.whl
Algorithm Hash digest
SHA256 b34e79a1fe8cbf6301b32455cf15e694f9156268226e8b3e61bf6c476f1940ba
MD5 880262a45c113f48cd1e467afdac8c29
BLAKE2b-256 e6b74c303c9f2165be7ed205446b605a95210d66ea882b13b0e145e9721aa9b5

See more details on using hashes here.

File details

Details for the file tensor_theorem_prover-0.14.0-cp38-none-win32.whl.

File metadata

File hashes

Hashes for tensor_theorem_prover-0.14.0-cp38-none-win32.whl
Algorithm Hash digest
SHA256 d2f2c586f8c0af5bc567be1306771dff3aec06336514e4b3432eaa4fee0d8338
MD5 bdd73cddafd382427931e097d5d167b3
BLAKE2b-256 bc5a97d3039c17bcb67390e6e6ed08868c7b108b2db09173a22339983c3d55e6

See more details on using hashes here.

File details

Details for the file tensor_theorem_prover-0.14.0-cp38-cp38-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for tensor_theorem_prover-0.14.0-cp38-cp38-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 0c8407daaa90da7b91a43fa586f04ed4e48ad79f563ad1e88db58829d71d121f
MD5 98c7e5c9582c957a07e8a61f32a8ed21
BLAKE2b-256 20eef455947b38139a5232e807050a462d3a7f9fd7d524119c362ff11ee57418

See more details on using hashes here.

File details

Details for the file tensor_theorem_prover-0.14.0-cp38-cp38-musllinux_1_2_i686.whl.

File metadata

File hashes

Hashes for tensor_theorem_prover-0.14.0-cp38-cp38-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 1b3f37a3772d532c1a231a18e5cf7857c2989aafd66f9b32aaf421f6430fee80
MD5 fe0f86f6c47cb410f092aa0ba40fc2f7
BLAKE2b-256 1034236f779f29f1f6e639e5c3ae8999f77eed2a8b7fc31154d8fa3ec33408b9

See more details on using hashes here.

File details

Details for the file tensor_theorem_prover-0.14.0-cp38-cp38-musllinux_1_2_armv7l.whl.

File metadata

File hashes

Hashes for tensor_theorem_prover-0.14.0-cp38-cp38-musllinux_1_2_armv7l.whl
Algorithm Hash digest
SHA256 922b21f16d54ff585c3a3d5e486031be49036438b509163e9f7818cfd239e095
MD5 0547ec904c1258f62f46d2f81124fd2f
BLAKE2b-256 352cc74acb14fb783a446594fbe2a5589a5e1b5aa979a676bd044f992e7e55af

See more details on using hashes here.

File details

Details for the file tensor_theorem_prover-0.14.0-cp38-cp38-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for tensor_theorem_prover-0.14.0-cp38-cp38-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 8f8c589196579120e39d7642a879410cdfd34cbd4b836d5594b41755310a9c57
MD5 306eae88b1ef148369a1c1bbc3a292fc
BLAKE2b-256 81f8f0287201a15a0dc0d509559b50e33102ff188b9bc9e301587fe4456f0bee

See more details on using hashes here.

File details

Details for the file tensor_theorem_prover-0.14.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for tensor_theorem_prover-0.14.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 feeac8c591e8eebc9e542eca60f23c856049f4c20cf05a57343f2f49c656799b
MD5 a47676e27710be30288d6df19bd5ba26
BLAKE2b-256 9393ed422925fcac337654b1c05de6a931ab8d890cb51a390d77fac45b584b77

See more details on using hashes here.

File details

Details for the file tensor_theorem_prover-0.14.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl.

File metadata

File hashes

Hashes for tensor_theorem_prover-0.14.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl
Algorithm Hash digest
SHA256 d6215ff770fa3bb580ad38d7cd0504c89827b560e0321bf8da104cd7f415afaf
MD5 52b69e718d298095f63de8a25edbd7f5
BLAKE2b-256 61ab2962eb257f44c7cc886c21ac1b90a45d4930e4adfd916e3d621b54c653a7

See more details on using hashes here.

File details

Details for the file tensor_theorem_prover-0.14.0-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl.

File metadata

File hashes

Hashes for tensor_theorem_prover-0.14.0-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl
Algorithm Hash digest
SHA256 b29817d44332fc040618fea3b4c3b4c76d78adf53f787e45f619eb2e8ec7ea4a
MD5 45e20a94a1fb3e4eecc06998becec649
BLAKE2b-256 e20ca0e7339552c2553c82e6ed1a1c0409bff56df011c4b59ecbe327c20bf400

See more details on using hashes here.

File details

Details for the file tensor_theorem_prover-0.14.0-cp38-cp38-macosx_10_7_x86_64.whl.

File metadata

File hashes

Hashes for tensor_theorem_prover-0.14.0-cp38-cp38-macosx_10_7_x86_64.whl
Algorithm Hash digest
SHA256 5f72b418d01580c61b31fd0c00bca757b41cb0d9cd71df4dbaf89b53b311b124
MD5 ae7bc27a7b203e464607d2e0c60fcff3
BLAKE2b-256 8945a0c9a8a6151489b3873b3cf07fbf81b5b45b0dc60559c2a370f4792b065c

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page