Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
6facc2c
Add unit tests and update api reference
ColtonPayne Aug 8, 2025
6990e03
Demo floating point wierdness
ColtonPayne Aug 8, 2025
307004e
Add pre-commit hook for pytest
ColtonPayne Aug 8, 2025
5522d33
Upd pre-commit
ColtonPayne Aug 8, 2025
6cfc330
Upd
ColtonPayne Aug 8, 2025
87bb9c7
Add pre-push hook for functional tests
ColtonPayne Aug 8, 2025
53d3bc5
Add contributing.md
ColtonPayne Aug 8, 2025
84c89c8
Add ruff linter
ColtonPayne Aug 8, 2025
ce96151
Revert "Add ruff linter"
ColtonPayne Aug 8, 2025
a4f136f
Add support for ruff linter
ColtonPayne Aug 8, 2025
5af8466
Add a comment
ColtonPayne Aug 11, 2025
e618cdd
add initial classifier tests
ColtonPayne Aug 18, 2025
198d20e
Merge pull request #1 from ColtonPayne/add-pytest
ColtonPayne Aug 24, 2025
da0621b
test: add JIT consistency check for hello world
ColtonPayne Aug 24, 2025
ff247ab
Merge pull request #2 from ColtonPayne/codex/add-tests-for-jit-and-pu…
ColtonPayne Aug 24, 2025
d88f451
test: revise hello world consistency
ColtonPayne Aug 24, 2025
9b63e31
Merge branch 'main' into codex/add-tests-for-jit-and-pure-python-cons…
ColtonPayne Aug 24, 2025
d5f1296
Merge pull request #3 from ColtonPayne/codex/add-tests-for-jit-and-pu…
ColtonPayne Aug 24, 2025
609a3bf
test: expand _ground_rule coverage
ColtonPayne Aug 24, 2025
2c4dd66
test: fix interval constructor for hello world consistency
ColtonPayne Aug 24, 2025
a947acb
Merge branch 'main' into codex/add-tests-for-jit-and-pure-python-cons…
ColtonPayne Aug 24, 2025
e7cfe09
test: filter edge groundings when node clause narrows
ColtonPayne Aug 24, 2025
a382cf9
Merge pull request #5 from ColtonPayne/codex/add-tests-for-jit-and-pu…
ColtonPayne Aug 24, 2025
7d54a2c
test: cover node recheck failure branch
ColtonPayne Aug 24, 2025
8c15823
test: cover edge head node reuse
ColtonPayne Aug 24, 2025
44b25de
test: cover edge recheck in _ground_rule
ColtonPayne Aug 24, 2025
c8ef7a8
Merge pull request #4 from ColtonPayne/codex/add-tests-for-full-branc…
ColtonPayne Aug 24, 2025
bfca28a
Pull in new changesz
ColtonPayne Aug 31, 2025
132f5ee
Merge pull request #8 from ColtonPayne/lab-v2-main
ColtonPayne Aug 31, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
__pycache__/
*.py[cod]
*$py.class
.cache_status.yaml
.DS_STORE

# C extensions
*.so
Expand Down Expand Up @@ -84,6 +86,9 @@ target/
profile_default/
ipython_config.py

#VS Code configs
.vscode/settings.json

# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
Expand Down
18 changes: 18 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
repos:
- repo: local
hooks:
# --- Unit tests ---
- id: pytest-unit-with-coverage
name: Run unit tests with coverage and open report
entry: bash -c 'pytest tests/unit/disable_jit --cov pyreason --cov-report=html && open htmlcov/index.html'
language: system
pass_filenames: false

# --- Functional tests ---
- id: pytest-functional
name: Run functional tests
entry: bash -c 'pytest tests/functional'
language: system
pass_filenames: false
# Only run manually, not on every commit
stages: [pre-push]
16 changes: 16 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: not justMyCode",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": false
}
]
}
31 changes: 31 additions & 0 deletions contributing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

## Setting up Pre-Commit Hooks

To ensure code quality and consistency, set up the pre-commit hooks by running the following command:

```bash
pre-commit install --hook-type pre-commit --hook-type pre-push
```

This will configure the necessary hooks to run automatically during commits and pushes.

# Linting

We are working to update the codebase to comply with the `ruff` linting rules. Run this command to view linting:
```bash
ruff check .
```


## Running Tests

This codebase has a unit and functional test suite. You can run the unit tests using `pytest` with the following command:

```bash
pytest tests/unit
```

```bash
pytest tests/functional
```

5 changes: 5 additions & 0 deletions docs/source/api_reference/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ API Documentation


.. automodule:: pyreason
:members:
:undoc-members:
:show-inheritance:

.. automodule:: classifier
:members:
:undoc-members:
:show-inheritance:
2 changes: 1 addition & 1 deletion pyreason/.cache_status.yaml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
initialized: false
initialized: true
6 changes: 3 additions & 3 deletions pyreason/scripts/annotation_functions/annotation_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ def _check_bound(lower, upper):
if lower > upper:
return (0, 1)
else:
l = min(lower, 1)
u = min(upper, 1)
return (l, u)
lower = min(lower, 1)
upper = min(upper, 1)
return (lower, upper)


@numba.njit
Expand Down
11 changes: 4 additions & 7 deletions pyreason/scripts/components/world.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ class World:
def __init__(self, labels):
self._labels = labels
self._world = numba.typed.Dict.empty(key_type=label.label_type, value_type=interval.interval_type)
for l in labels:
self._world[l] = interval.closed(0.0, 1.0)
for my_label in labels:
self._world[my_label] = interval.closed(0.0, 1.0)

@property
def world(self):
Expand All @@ -29,9 +29,6 @@ def is_satisfied(self, label, interval):
return result

def update(self, label, interval):
lwanted = None
bwanted = None

current_bnd = self._world[label]
new_bnd = current_bnd.intersection(interval)
self._world[label] = new_bnd
Expand All @@ -48,7 +45,7 @@ def get_world(self):

def __str__(self):
result = ''
for label in self._world.keys():
result = result + label.get_value() + ',' + self._world[label].to_str() + '\n'
for my_label in self._world.keys():
result = result + my_label.get_value() + ',' + self._world[my_label].to_str() + '\n'

return result
41 changes: 23 additions & 18 deletions pyreason/scripts/learning/classification/classifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,29 @@ def forward(self, x, t1: int = 0, t2: int = 0) -> Tuple[torch.Tensor, torch.Tens

# Convert logits to probabilities assuming a multi-class classification.
probabilities = F.softmax(output, dim=1).squeeze()
opts = self.interface_options
print("Probs: ", probabilities)


# Convert bounds to Python floats for fact creation.
lower_bounds, upper_bounds = self.calculate_bounds(probabilities)
bounds_list = []
for i in range(len(self.class_names)):
lower = lower_bounds[i].item()
upper = upper_bounds[i].item()
bounds_list.append([lower, upper])

# Define time bounds for the facts.
facts = []
for class_name, bounds in zip(self.class_names, bounds_list):
lower, upper = bounds
fact_str = f'{class_name}({self.identifier}) : [{lower:.3f}, {upper:.3f}]'
fact = Fact(fact_str, name=f'{self.identifier}-{class_name}-fact', start_time=t1, end_time=t2)
facts.append(fact)
return output, probabilities, facts


def calculate_bounds(self, probabilities):
opts = self.interface_options
# Prepare threshold tensor.
threshold = torch.tensor(opts.threshold, dtype=probabilities.dtype, device=probabilities.device)
condition = probabilities > threshold
Expand All @@ -72,20 +93,4 @@ def forward(self, x, t1: int = 0, t2: int = 0) -> Tuple[torch.Tensor, torch.Tens
# For probabilities that pass the threshold, apply the above; else, bounds are fixed to [0,1].
lower_bounds = torch.where(condition, lower_val, torch.zeros_like(probabilities))
upper_bounds = torch.where(condition, upper_val, torch.ones_like(probabilities))

# Convert bounds to Python floats for fact creation.
bounds_list = []
for i in range(len(self.class_names)):
lower = lower_bounds[i].item()
upper = upper_bounds[i].item()
bounds_list.append([lower, upper])

# Define time bounds for the facts.
facts = []
for class_name, bounds in zip(self.class_names, bounds_list):
lower, upper = bounds
fact_str = f'{class_name}({self.identifier}) : [{lower:.3f}, {upper:.3f}]'
fact = Fact(fact_str, name=f'{self.identifier}-{class_name}-fact', start_time=t1, end_time=t2)
facts.append(fact)
return output, probabilities, facts

return lower_bounds, upper_bounds
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ numba==0.59.1
numpy==1.26.4
memory_profiler
pytest
torch
setuptools_scm
pytest-cov
pre-commit

sphinx_rtd_theme
sphinx
Expand Down
Empty file.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import pyreason as pr
import numba
import numpy as np
from pyreason.scripts.numba_wrapper.numba_types.interval_type import closed


@numba.njit
Expand All @@ -13,6 +14,17 @@ def probability_func(annotations, weights):
return union_prob, 1


def test_probability_func_consistency():
"""Ensure annotation function behaves the same with and without JIT."""
annotations = numba.typed.List()
annotations.append(numba.typed.List([closed(0.01, 1.0)]))
annotations.append(numba.typed.List([closed(0.2, 1.0)]))
weights = numba.typed.List([1.0, 1.0])
jit_res = probability_func(annotations, weights)
py_res = probability_func.py_func(annotations, weights)
assert jit_res == py_res


def test_annotation_function():
# Reset PyReason
pr.reset()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


def test_anyBurl_rule_1():
graph_path = './tests/knowledge_graph_test_subset.graphml'
graph_path = './tests/functional/knowledge_graph_test_subset.graphml'
pr.reset()
pr.reset_rules()
pr.reset_settings()
Expand Down Expand Up @@ -36,7 +36,7 @@ def test_anyBurl_rule_1():


def test_anyBurl_rule_2():
graph_path = './tests/knowledge_graph_test_subset.graphml'
graph_path = './tests/functional/knowledge_graph_test_subset.graphml'
pr.reset()
pr.reset_rules()
pr.reset_settings()
Expand Down Expand Up @@ -72,7 +72,7 @@ def test_anyBurl_rule_2():


def test_anyBurl_rule_3():
graph_path = './tests/knowledge_graph_test_subset.graphml'
graph_path = './tests/functional/knowledge_graph_test_subset.graphml'
pr.reset()
pr.reset_rules()
pr.reset_settings()
Expand Down Expand Up @@ -108,7 +108,7 @@ def test_anyBurl_rule_3():


def test_anyBurl_rule_4():
graph_path = './tests/knowledge_graph_test_subset.graphml'
graph_path = './tests/functional/knowledge_graph_test_subset.graphml'
pr.reset()
pr.reset_rules()
pr.reset_settings()
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def test_custom_thresholds():
pr.reset_rules()

# Modify the paths based on where you've stored the files we made above
graph_path = "./tests/group_chat_graph.graphml"
graph_path = "./tests/functional/group_chat_graph.graphml"

# Modify pyreason settings to make verbose
pr.reset_settings()
Expand Down
44 changes: 42 additions & 2 deletions tests/test_hello_world.py → tests/functional/test_hello_world.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Test if the simple hello world program works
import pyreason as pr
#import pyreason as pr
import faulthandler
import json
import os
import subprocess
import sys
import textwrap

import pyreason.pyreason as pr


def test_hello_world():
Expand All @@ -10,7 +17,7 @@ def test_hello_world():
pr.reset_settings()

# Modify the paths based on where you've stored the files we made above
graph_path = './tests/friends_graph.graphml'
graph_path = './tests/functional/friends_graph.graphml'

# Modify pyreason settings to make verbose
pr.settings.verbose = True # Print info to screen
Expand All @@ -24,6 +31,7 @@ def test_hello_world():
# Run the program for two timesteps to see the diffusion take place
faulthandler.enable()
interpretation = pr.reason(timesteps=2)
print("Reasoning")

# Display the changes in the interpretation for each timestep
dataframes = pr.filter_and_sort_nodes(interpretation, ['popular'])
Expand All @@ -47,3 +55,35 @@ def test_hello_world():

# John should be popular in timestep 3
assert 'John' in dataframes[2]['component'].values and dataframes[2].iloc[1].popular == [1, 1], 'John should have popular bounds [1,1] for t=2 timesteps'

def test_hello_world_consistency():
"""Ensure hello world output matches when using JIT vs pure Python interval constructors."""

import pyreason.scripts.numba_wrapper.numba_types.interval_type as interval_type

def run():
pr.reset()
pr.reset_rules()
pr.reset_settings()
pr.settings.verbose = False
pr.load_graphml('./tests/functional/friends_graph.graphml')
pr.add_rule(pr.Rule('popular(x) <-1 popular(y), Friends(x,y), owns(y,z), owns(x,z)', 'popular_rule'))
pr.add_fact(pr.Fact('popular(Mary)', 'popular_fact', 0, 2))
interpretation = pr.reason(timesteps=2)
dataframes = pr.filter_and_sort_nodes(interpretation, ['popular'])
return [df[['component', 'popular']].to_dict('records') for df in dataframes]

original_closed = interval_type.closed
jit_res = run()
try:
from pyreason.scripts.interval.interval import Interval

def py_closed(lower, upper, static=False):
return Interval(float(lower), float(upper), static)

interval_type.closed = py_closed
py_res = run()
finally:
interval_type.closed = original_closed

assert jit_res == py_res
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def test_hello_world_parallel():
pr.reset_rules()

# Modify the paths based on where you've stored the files we made above
graph_path = './tests/friends_graph.graphml'
graph_path = './tests/functional/friends_graph.graphml'

# Modify pyreason settings to make verbose
pr.reset_settings()
Expand Down
2 changes: 1 addition & 1 deletion tests/test_num_ga.py → tests/functional/test_num_ga.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@


def test_num_ga():
graph_path = './tests/knowledge_graph_test_subset.graphml'
graph_path = './tests/functional/knowledge_graph_test_subset.graphml'
pr.reset()
pr.reset_rules()
pr.reset_settings()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def test_reason_again():
pr.reset_settings()

# Modify the paths based on where you've stored the files we made above
graph_path = './tests/friends_graph.graphml'
graph_path = './tests/functional/friends_graph.graphml'

# Modify pyreason settings to make verbose
pr.settings.verbose = True # Print info to screen
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def test_reorder_clauses():
pr.reset_settings()

# Modify the paths based on where you've stored the files we made above
graph_path = './tests/friends_graph.graphml'
graph_path = './tests/functional/friends_graph.graphml'

# Modify pyreason settings to make verbose
pr.settings.verbose = True # Print info to screen
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def test_rule_filtering():
pr.reset_settings()

# Modify the paths based on where you've stored the files we made above
graph_path = './tests/friends_graph.graphml'
graph_path = './tests/functional/friends_graph.graphml'

# Modify pyreason settings to make verbose
pr.settings.verbose = True # Print info to screen
Expand Down
Loading
Loading