Skip to content

Add unit tests and update api reference to include classifier module #69

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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,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()
# Modify pyreason settings to make verbose and to save the rule trace to a file
Expand Down Expand Up @@ -35,7 +35,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()
# Modify pyreason settings to make verbose and to save the rule trace to a file
Expand Down Expand Up @@ -70,7 +70,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()
# Modify pyreason settings to make verbose and to save the rule trace to a file
Expand Down Expand Up @@ -105,7 +105,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()
# Modify pyreason settings to make verbose and to save the rule trace to a file
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def test_classifier_integration():
)

# Create an instance of LogicIntegratedClassifier.
logic_classifier = pr.LogicIntegratedClassifier(model, class_names, model_name="classifier",
logic_classifier = pr.LogicIntegratedClassifier(model, class_names, "classifier",
interface_options=interface_options)

# Create a dummy input tensor with 10 features.
Expand Down
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Test if the simple hello world program works
import pyreason as pr
#import pyreason as pr
import faulthandler
import pyreason.pyreason as pr


def test_hello_world():
Expand All @@ -10,7 +11,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 +25,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 @@ -48,3 +50,4 @@ 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'

test_hello_world()
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()
# Modify pyreason settings to make verbose and to save the rule trace to a file
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
4 changes: 4 additions & 0 deletions tests/unit/disable_jit/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# # tests/conftest.py
import os; os.environ["NUMBA_DISABLE_JIT"] = "1"
import numba; numba.config.DISABLE_JIT = True
import sys, types; sys.modules.setdefault("pyreason.pyreason", types.ModuleType("pyreason.pyreason"))
Loading
Loading