diff --git a/.github/workflows/python-package-version-test.yml b/.github/workflows/python-package-version-test.yml index fb4a0da0..e4640689 100644 --- a/.github/workflows/python-package-version-test.yml +++ b/.github/workflows/python-package-version-test.yml @@ -16,23 +16,27 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 + - name: Cache Numba JIT artifacts + uses: actions/cache@v4 + with: + path: pyreason/cache + key: numba-${{ matrix.python-version }}-${{ hashFiles('pyreason/scripts/**/*.py') }} + restore-keys: | + numba-${{ matrix.python-version }}- - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + - name: Install uv + uses: astral-sh/setup-uv@v6 - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install ruff pytest - pip install torch==2.6.0 - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + run: uv pip install --system -e ".[dev,torch]" - name: Lint with ruff - run: | - python -m ruff check pyreason/scripts + run: python -m ruff check pyreason/scripts - name: Pytest Unit Tests with JIT Disabled run: | pytest tests/unit/disable_jit --tb=short -q diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 479faed3..f8c9ba9c 100755 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -21,9 +21,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 + with: + fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: '3.x' - name: Install dependencies diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 296c7943..a4952210 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -3,13 +3,16 @@ version: 2 build: - os: ubuntu-22.04 + os: ubuntu-24.04 tools: - python: "3.9" + python: "3.12" sphinx: configuration: docs/source/conf.py python: install: - - requirements: requirements.txt + - method: pip + path: . + extra_requirements: + - docs diff --git a/pyproject.toml b/pyproject.toml index bf5379f8..0441e5e1 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,64 @@ [build-system] -requires = ['setuptools>=42'] -build-backend = 'setuptools.build_meta' +requires = ["setuptools>=80", "setuptools-scm>=8"] +build-backend = "setuptools.build_meta" + +[project] +name = "pyreason" +description = "An explainable inference software supporting annotated, real valued, graph based and temporal logic" +readme = "README.md" +license = { text = "BSD 3-Clause" } +authors = [ + { name = "Dyuman Aditya", email = "dyuman.aditya@gmail.com" } +] +classifiers = [ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", +] +requires-python = ">=3.10" +dynamic = ["version"] +dependencies = [ + "networkx>=3.1", + "pyyaml>=6.0", + "pandas>=2.0.0", + "numba>=0.65.1", + "numpy>=2.1,<2.5", +] + +[project.optional-dependencies] +torch = ["torch>=2.6.0"] +dev = [ + "pytest", + "pytest-cov", + "pre-commit", + "ruff", + "memory_profiler", +] +docs = [ + "sphinx", + "sphinx_rtd_theme", + "sphinx-autopackagesummary", + "sphinx-autoapi", +] + +[project.urls] +Homepage = "https://github.com/lab-v2/pyreason" +"Bug Tracker" = "https://github.com/lab-v2/pyreason/issues" +Repository = "https://github.com/lab-v2/pyreason" + +[tool.setuptools_scm] +fallback_version = "0.0.0" + +[tool.setuptools] +include-package-data = true + +[tool.setuptools.packages.find] +where = ["."] [tool.ruff.lint] # Ignore ambiguous variable name errors (E741) in interpretation files diff --git a/pyreason/__init__.py b/pyreason/__init__.py index d0611ae1..08a67d83 100755 --- a/pyreason/__init__.py +++ b/pyreason/__init__.py @@ -1,6 +1,11 @@ # ruff: noqa: F403 F405 (Ignore Pyreason import * for public api) # Set numba environment variable import os +import sys + +# Python 3.11+ bytecode generates ~2300 CFG nodes for the large JIT-compiled +# reasoning functions; Numba's recursive DFS exceeds the default limit of 1000. +sys.setrecursionlimit(max(sys.getrecursionlimit(), 5000)) package_path = os.path.abspath(os.path.dirname(__file__)) cache_path = os.path.join(package_path, 'cache') cache_status_path = os.path.join(package_path, '.cache_status.yaml') @@ -9,13 +14,11 @@ from pyreason.pyreason import * import yaml -from importlib.metadata import version -from pkg_resources import get_distribution, DistributionNotFound +from importlib.metadata import version, PackageNotFoundError try: - __version__ = get_distribution(__name__).version -except DistributionNotFound: - # package is not installed + __version__ = version(__name__) +except PackageNotFoundError: pass diff --git a/pyreason/pyreason.py b/pyreason/pyreason.py index 52fa3cf6..4d70ec36 100755 --- a/pyreason/pyreason.py +++ b/pyreason/pyreason.py @@ -7,7 +7,12 @@ import time import sys import pandas as pd -import memory_profiler as mp +try: + import memory_profiler as mp + _has_memory_profiler = True +except ImportError: + mp = None + _has_memory_profiler = False import warnings from typing import List, Type, Callable, Tuple, Optional @@ -1513,6 +1518,12 @@ def reason(timesteps: int = -1, convergence_threshold: int = -1, convergence_bou if settings.output_to_file: sys.stdout = open(f"./{settings.output_file_name}_{__timestamp}.txt", "a") + if settings.memory_profile and not _has_memory_profiler: + raise ImportError( + "memory_profiler is required for memory profiling. " + "Install it with: pip install memory_profiler" + ) + if not again or __program is None: if settings.memory_profile: start_mem = mp.memory_usage(max_usage=True) diff --git a/pyreason/scripts/interpretation/interpretation.py b/pyreason/scripts/interpretation/interpretation.py index c2e1a187..4bc3d800 100755 --- a/pyreason/scripts/interpretation/interpretation.py +++ b/pyreason/scripts/interpretation/interpretation.py @@ -282,9 +282,9 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi facts_to_be_applied_node_new.clear() facts_to_be_applied_node_trace_new.clear() nodes_set = set(nodes) - for i in range(len(facts_to_be_applied_node)): - if facts_to_be_applied_node[i][0] == t: - comp, l, bnd, static, graph_attribute = facts_to_be_applied_node[i][1], facts_to_be_applied_node[i][2], facts_to_be_applied_node[i][3], facts_to_be_applied_node[i][4], facts_to_be_applied_node[i][5] + for fi in range(len(facts_to_be_applied_node)): + if facts_to_be_applied_node[fi][0] == t: + comp, l, bnd, static, graph_attribute = facts_to_be_applied_node[fi][1], facts_to_be_applied_node[fi][2], facts_to_be_applied_node[fi][3], facts_to_be_applied_node[fi][4], facts_to_be_applied_node[fi][5] # If the component is not in the graph, add it if comp not in nodes_set: _add_node(comp, neighbors, reverse_neighbors, nodes, interpretations_node) @@ -295,26 +295,26 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi # Check if we should even store any of the changes to the rule trace etc. # Inverse of this is: if not save_graph_attributes_to_rule_trace and graph_attribute if (save_graph_attributes_to_rule_trace or not graph_attribute) and store_interpretation_changes: - meta_name = facts_to_be_applied_node_trace[i] if atom_trace else '' + meta_name = facts_to_be_applied_node_trace[fi] if atom_trace else '' rule_trace_node.append((numba.types.uint16(t), numba.types.uint16(fp_cnt), comp, l, bnd, True, 'Fact', meta_name, '')) if atom_trace: - _update_rule_trace(rule_trace_node_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), bnd, facts_to_be_applied_node_trace[i]) + _update_rule_trace(rule_trace_node_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), bnd, facts_to_be_applied_node_trace[fi]) for p1, p2 in ipl: if p1==l: rule_trace_node.append((numba.types.uint16(t), numba.types.uint16(fp_cnt), comp, p2, interpretations_node[comp].world[p2], True, 'IPL', f'IPL: {l.get_value()}', '')) if atom_trace: - _update_rule_trace(rule_trace_node_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), interpretations_node[comp].world[p2], facts_to_be_applied_node_trace[i]) + _update_rule_trace(rule_trace_node_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), interpretations_node[comp].world[p2], facts_to_be_applied_node_trace[fi]) elif p2==l: rule_trace_node.append((numba.types.uint16(t), numba.types.uint16(fp_cnt), comp, p1, interpretations_node[comp].world[p1], True, 'IPL', f'IPL: {l.get_value()}', '')) if atom_trace: - _update_rule_trace(rule_trace_node_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), interpretations_node[comp].world[p1], facts_to_be_applied_node_trace[i]) + _update_rule_trace(rule_trace_node_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), interpretations_node[comp].world[p1], facts_to_be_applied_node_trace[fi]) else: # Check for inconsistencies (multiple facts) if check_consistent_node(interpretations_node, comp, (l, bnd)): mode = 'graph-attribute-fact' if graph_attribute else 'fact' override = True if update_mode == 'override' else False - u, changes = _update_node(interpretations_node, predicate_map_node, comp, (l, bnd), ipl, rule_trace_node, fp_cnt, t, static, convergence_mode, atom_trace, save_graph_attributes_to_rule_trace, rules_to_be_applied_node_trace, i, facts_to_be_applied_node_trace, rule_trace_node_atoms, store_interpretation_changes, num_ga, mode=mode, override=override) + u, changes = _update_node(interpretations_node, predicate_map_node, comp, (l, bnd), ipl, rule_trace_node, fp_cnt, t, static, convergence_mode, atom_trace, save_graph_attributes_to_rule_trace, rules_to_be_applied_node_trace, fi, facts_to_be_applied_node_trace, rule_trace_node_atoms, store_interpretation_changes, num_ga, mode=mode, override=override) update = u or update # Update convergence params @@ -326,9 +326,9 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi else: mode = 'graph-attribute-fact' if graph_attribute else 'fact' if inconsistency_check: - resolve_inconsistency_node(interpretations_node, comp, (l, bnd), ipl, t, fp_cnt, i, atom_trace, rule_trace_node, rule_trace_node_atoms, rules_to_be_applied_node_trace, facts_to_be_applied_node_trace, store_interpretation_changes, mode=mode) + resolve_inconsistency_node(interpretations_node, comp, (l, bnd), ipl, t, fp_cnt, fi, atom_trace, rule_trace_node, rule_trace_node_atoms, rules_to_be_applied_node_trace, facts_to_be_applied_node_trace, store_interpretation_changes, mode=mode) else: - u, changes = _update_node(interpretations_node, predicate_map_node, comp, (l, bnd), ipl, rule_trace_node, fp_cnt, t, static, convergence_mode, atom_trace, save_graph_attributes_to_rule_trace, rules_to_be_applied_node_trace, i, facts_to_be_applied_node_trace, rule_trace_node_atoms, store_interpretation_changes, num_ga, mode=mode, override=True) + u, changes = _update_node(interpretations_node, predicate_map_node, comp, (l, bnd), ipl, rule_trace_node, fp_cnt, t, static, convergence_mode, atom_trace, save_graph_attributes_to_rule_trace, rules_to_be_applied_node_trace, fi, facts_to_be_applied_node_trace, rule_trace_node_atoms, store_interpretation_changes, num_ga, mode=mode, override=True) update = u or update # Update convergence params @@ -338,15 +338,15 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi changes_cnt += changes if static: - facts_to_be_applied_node_new.append((numba.types.uint16(facts_to_be_applied_node[i][0]+1), comp, l, bnd, static, graph_attribute)) + facts_to_be_applied_node_new.append((numba.types.uint16(facts_to_be_applied_node[fi][0]+1), comp, l, bnd, static, graph_attribute)) if atom_trace: - facts_to_be_applied_node_trace_new.append(facts_to_be_applied_node_trace[i]) + facts_to_be_applied_node_trace_new.append(facts_to_be_applied_node_trace[fi]) # If time doesn't match, fact to be applied later else: - facts_to_be_applied_node_new.append(facts_to_be_applied_node[i]) + facts_to_be_applied_node_new.append(facts_to_be_applied_node[fi]) if atom_trace: - facts_to_be_applied_node_trace_new.append(facts_to_be_applied_node_trace[i]) + facts_to_be_applied_node_trace_new.append(facts_to_be_applied_node_trace[fi]) # Update list of facts with ones that have not been applied yet (delete applied facts) facts_to_be_applied_node[:] = facts_to_be_applied_node_new.copy() @@ -359,9 +359,9 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi facts_to_be_applied_edge_new.clear() facts_to_be_applied_edge_trace_new.clear() edges_set = set(edges) - for i in range(len(facts_to_be_applied_edge)): - if facts_to_be_applied_edge[i][0]==t: - comp, l, bnd, static, graph_attribute = facts_to_be_applied_edge[i][1], facts_to_be_applied_edge[i][2], facts_to_be_applied_edge[i][3], facts_to_be_applied_edge[i][4], facts_to_be_applied_edge[i][5] + for fi in range(len(facts_to_be_applied_edge)): + if facts_to_be_applied_edge[fi][0]==t: + comp, l, bnd, static, graph_attribute = facts_to_be_applied_edge[fi][1], facts_to_be_applied_edge[fi][2], facts_to_be_applied_edge[fi][3], facts_to_be_applied_edge[fi][4], facts_to_be_applied_edge[fi][5] # If the component is not in the graph, add it if comp not in edges_set: _add_edge(comp[0], comp[1], neighbors, reverse_neighbors, nodes, edges, label.Label(''), interpretations_node, interpretations_edge, predicate_map_edge, num_ga, t) @@ -371,25 +371,25 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi if l in interpretations_edge[comp].world and interpretations_edge[comp].world[l].is_static(): # Inverse of this is: if not save_graph_attributes_to_rule_trace and graph_attribute if (save_graph_attributes_to_rule_trace or not graph_attribute) and store_interpretation_changes: - meta_name = facts_to_be_applied_edge_trace[i] if atom_trace else '' + meta_name = facts_to_be_applied_edge_trace[fi] if atom_trace else '' rule_trace_edge.append((numba.types.uint16(t), numba.types.uint16(fp_cnt), comp, l, interpretations_edge[comp].world[l], True, 'Fact', meta_name, '')) if atom_trace: - _update_rule_trace(rule_trace_edge_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), bnd, facts_to_be_applied_edge_trace[i]) + _update_rule_trace(rule_trace_edge_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), bnd, facts_to_be_applied_edge_trace[fi]) for p1, p2 in ipl: if p1==l: rule_trace_edge.append((numba.types.uint16(t), numba.types.uint16(fp_cnt), comp, p2, interpretations_edge[comp].world[p2], True, 'IPL', f'IPL: {l.get_value()}', '')) if atom_trace: - _update_rule_trace(rule_trace_edge_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), interpretations_edge[comp].world[p2], facts_to_be_applied_edge_trace[i]) + _update_rule_trace(rule_trace_edge_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), interpretations_edge[comp].world[p2], facts_to_be_applied_edge_trace[fi]) elif p2==l: rule_trace_edge.append((numba.types.uint16(t), numba.types.uint16(fp_cnt), comp, p1, interpretations_edge[comp].world[p1], True, 'IPL', f'IPL: {l.get_value()}', '')) if atom_trace: - _update_rule_trace(rule_trace_edge_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), interpretations_edge[comp].world[p1], facts_to_be_applied_edge_trace[i]) + _update_rule_trace(rule_trace_edge_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), interpretations_edge[comp].world[p1], facts_to_be_applied_edge_trace[fi]) else: # Check for inconsistencies if check_consistent_edge(interpretations_edge, comp, (l, bnd)): mode = 'graph-attribute-fact' if graph_attribute else 'fact' override = True if update_mode == 'override' else False - u, changes = _update_edge(interpretations_edge, predicate_map_edge, comp, (l, bnd), ipl, rule_trace_edge, fp_cnt, t, static, convergence_mode, atom_trace, save_graph_attributes_to_rule_trace, rules_to_be_applied_edge_trace, i, facts_to_be_applied_edge_trace, rule_trace_edge_atoms, store_interpretation_changes, num_ga, mode=mode, override=override) + u, changes = _update_edge(interpretations_edge, predicate_map_edge, comp, (l, bnd), ipl, rule_trace_edge, fp_cnt, t, static, convergence_mode, atom_trace, save_graph_attributes_to_rule_trace, rules_to_be_applied_edge_trace, fi, facts_to_be_applied_edge_trace, rule_trace_edge_atoms, store_interpretation_changes, num_ga, mode=mode, override=override) update = u or update # Update convergence params @@ -401,9 +401,9 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi else: mode = 'graph-attribute-fact' if graph_attribute else 'fact' if inconsistency_check: - resolve_inconsistency_edge(interpretations_edge, comp, (l, bnd), ipl, t, fp_cnt, i, atom_trace, rule_trace_edge, rule_trace_edge_atoms, rules_to_be_applied_edge_trace, facts_to_be_applied_edge_trace, store_interpretation_changes, mode=mode) + resolve_inconsistency_edge(interpretations_edge, comp, (l, bnd), ipl, t, fp_cnt, fi, atom_trace, rule_trace_edge, rule_trace_edge_atoms, rules_to_be_applied_edge_trace, facts_to_be_applied_edge_trace, store_interpretation_changes, mode=mode) else: - u, changes = _update_edge(interpretations_edge, predicate_map_edge, comp, (l, bnd), ipl, rule_trace_edge, fp_cnt, t, static, convergence_mode, atom_trace, save_graph_attributes_to_rule_trace, rules_to_be_applied_edge_trace, i, facts_to_be_applied_edge_trace, rule_trace_edge_atoms, store_interpretation_changes, num_ga, mode=mode, override=True) + u, changes = _update_edge(interpretations_edge, predicate_map_edge, comp, (l, bnd), ipl, rule_trace_edge, fp_cnt, t, static, convergence_mode, atom_trace, save_graph_attributes_to_rule_trace, rules_to_be_applied_edge_trace, fi, facts_to_be_applied_edge_trace, rule_trace_edge_atoms, store_interpretation_changes, num_ga, mode=mode, override=True) update = u or update # Update convergence params @@ -413,15 +413,15 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi changes_cnt += changes if static: - facts_to_be_applied_edge_new.append((numba.types.uint16(facts_to_be_applied_edge[i][0]+1), comp, l, bnd, static, graph_attribute)) + facts_to_be_applied_edge_new.append((numba.types.uint16(facts_to_be_applied_edge[fi][0]+1), comp, l, bnd, static, graph_attribute)) if atom_trace: - facts_to_be_applied_edge_trace_new.append(facts_to_be_applied_edge_trace[i]) + facts_to_be_applied_edge_trace_new.append(facts_to_be_applied_edge_trace[fi]) # Time doesn't match, fact to be applied later else: - facts_to_be_applied_edge_new.append(facts_to_be_applied_edge[i]) + facts_to_be_applied_edge_new.append(facts_to_be_applied_edge[fi]) if atom_trace: - facts_to_be_applied_edge_trace_new.append(facts_to_be_applied_edge_trace[i]) + facts_to_be_applied_edge_trace_new.append(facts_to_be_applied_edge_trace[fi]) # Update list of facts with ones that have not been applied yet (delete applied facts) facts_to_be_applied_edge[:] = facts_to_be_applied_edge_new.copy() @@ -470,10 +470,10 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi rules_to_remove_idx.add(idx) # Remove from rules to be applied and edges to be applied lists after coming out from loop - rules_to_be_applied_node[:] = numba.typed.List([rules_to_be_applied_node[i] for i in range(len(rules_to_be_applied_node)) if i not in rules_to_remove_idx]) - edges_to_be_added_node_rule[:] = numba.typed.List([edges_to_be_added_node_rule[i] for i in range(len(edges_to_be_added_node_rule)) if i not in rules_to_remove_idx]) + rules_to_be_applied_node[:] = numba.typed.List([rules_to_be_applied_node[j] for j in range(len(rules_to_be_applied_node)) if j not in rules_to_remove_idx]) + edges_to_be_added_node_rule[:] = numba.typed.List([edges_to_be_added_node_rule[j] for j in range(len(edges_to_be_added_node_rule)) if j not in rules_to_remove_idx]) if atom_trace: - rules_to_be_applied_node_trace[:] = numba.typed.List([rules_to_be_applied_node_trace[i] for i in range(len(rules_to_be_applied_node_trace)) if i not in rules_to_remove_idx]) + rules_to_be_applied_node_trace[:] = numba.typed.List([rules_to_be_applied_node_trace[j] for j in range(len(rules_to_be_applied_node_trace)) if j not in rules_to_remove_idx]) # Edges rules_to_remove_idx.clear() @@ -544,10 +544,10 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi rules_to_remove_idx.add(idx) # Remove from rules to be applied and edges to be applied lists after coming out from loop - rules_to_be_applied_edge[:] = numba.typed.List([rules_to_be_applied_edge[i] for i in range(len(rules_to_be_applied_edge)) if i not in rules_to_remove_idx]) - edges_to_be_added_edge_rule[:] = numba.typed.List([edges_to_be_added_edge_rule[i] for i in range(len(edges_to_be_added_edge_rule)) if i not in rules_to_remove_idx]) + rules_to_be_applied_edge[:] = numba.typed.List([rules_to_be_applied_edge[j] for j in range(len(rules_to_be_applied_edge)) if j not in rules_to_remove_idx]) + edges_to_be_added_edge_rule[:] = numba.typed.List([edges_to_be_added_edge_rule[j] for j in range(len(edges_to_be_added_edge_rule)) if j not in rules_to_remove_idx]) if atom_trace: - rules_to_be_applied_edge_trace[:] = numba.typed.List([rules_to_be_applied_edge_trace[i] for i in range(len(rules_to_be_applied_edge_trace)) if i not in rules_to_remove_idx]) + rules_to_be_applied_edge_trace[:] = numba.typed.List([rules_to_be_applied_edge_trace[j] for j in range(len(rules_to_be_applied_edge_trace)) if j not in rules_to_remove_idx]) # Fixed point if update: @@ -568,13 +568,13 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi in_loop_threadsafe.append(False) update_threadsafe.append(True) - for i in prange(len(rules)): - rule = rules[i] + for ri in prange(len(rules)): + rule = rules[ri] # Only go through if the rule can be applied within the given timesteps, or we're running until convergence delta_t = rule.get_delta() if t + delta_t <= tmax or tmax == -1 or again: - applicable_node_rules, applicable_edge_rules = _ground_rule(rule, interpretations_node, interpretations_edge, predicate_map_node, predicate_map_edge, nodes, edges, neighbors, reverse_neighbors, atom_trace, extended_ann_fn_flags[i], allow_ground_rules, num_ga, t, head_functions, closed_world_predicates) + applicable_node_rules, applicable_edge_rules = _ground_rule(rule, interpretations_node, interpretations_edge, predicate_map_node, predicate_map_edge, nodes, edges, neighbors, reverse_neighbors, atom_trace, extended_ann_fn_flags[ri], allow_ground_rules, num_ga, t, head_functions, closed_world_predicates) # Loop through applicable rules and add them to the rules to be applied for later or next fp operation for applicable_rule in applicable_node_rules: @@ -592,14 +592,14 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi bnd_u = min(max(bnd[1], 0), 1) bnd = interval.closed(bnd_l, bnd_u) max_rules_time = max(max_rules_time, t + delta_t) - rules_to_be_applied_node_threadsafe[i].append((numba.types.uint16(t + delta_t), n, rule.get_target(), bnd, rule.is_static_rule())) + rules_to_be_applied_node_threadsafe[ri].append((numba.types.uint16(t + delta_t), n, rule.get_target(), bnd, rule.is_static_rule())) if atom_trace: - rules_to_be_applied_node_trace_threadsafe[i].append((qualified_nodes, qualified_edges, rule.get_name())) + rules_to_be_applied_node_trace_threadsafe[ri].append((qualified_nodes, qualified_edges, rule.get_name())) # If delta_t is zero we apply the rules and check if more are applicable if delta_t == 0: - in_loop_threadsafe[i] = True - update_threadsafe[i] = False + in_loop_threadsafe[ri] = True + update_threadsafe[ri] = False for applicable_rule in applicable_edge_rules: e, annotations, qualified_nodes, qualified_edges, edges_to_add, clause_labels, clause_variables = applicable_rule @@ -617,38 +617,38 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi bnd = interval.closed(bnd_l, bnd_u) max_rules_time = max(max_rules_time, t+delta_t) # edges_to_be_added_edge_rule.append(edges_to_add) - edges_to_be_added_edge_rule_threadsafe[i].append(edges_to_add) - rules_to_be_applied_edge_threadsafe[i].append((numba.types.uint16(t+delta_t), e, rule.get_target(), bnd, rule.is_static_rule())) + edges_to_be_added_edge_rule_threadsafe[ri].append(edges_to_add) + rules_to_be_applied_edge_threadsafe[ri].append((numba.types.uint16(t+delta_t), e, rule.get_target(), bnd, rule.is_static_rule())) if atom_trace: # rules_to_be_applied_edge_trace.append((qualified_nodes, qualified_edges, rule.get_name())) - rules_to_be_applied_edge_trace_threadsafe[i].append((qualified_nodes, qualified_edges, rule.get_name())) + rules_to_be_applied_edge_trace_threadsafe[ri].append((qualified_nodes, qualified_edges, rule.get_name())) # If delta_t is zero we apply the rules and check if more are applicable if delta_t == 0: - in_loop_threadsafe[i] = True - update_threadsafe[i] = False + in_loop_threadsafe[ri] = True + update_threadsafe[ri] = False # Update lists after parallel run - for i in range(len(rules)): - if len(rules_to_be_applied_node_threadsafe[i]) > 0: - rules_to_be_applied_node.extend(rules_to_be_applied_node_threadsafe[i]) - if len(rules_to_be_applied_edge_threadsafe[i]) > 0: - rules_to_be_applied_edge.extend(rules_to_be_applied_edge_threadsafe[i]) + for ri in range(len(rules)): + if len(rules_to_be_applied_node_threadsafe[ri]) > 0: + rules_to_be_applied_node.extend(rules_to_be_applied_node_threadsafe[ri]) + if len(rules_to_be_applied_edge_threadsafe[ri]) > 0: + rules_to_be_applied_edge.extend(rules_to_be_applied_edge_threadsafe[ri]) if atom_trace: - if len(rules_to_be_applied_node_trace_threadsafe[i]) > 0: - rules_to_be_applied_node_trace.extend(rules_to_be_applied_node_trace_threadsafe[i]) - if len(rules_to_be_applied_edge_trace_threadsafe[i]) > 0: - rules_to_be_applied_edge_trace.extend(rules_to_be_applied_edge_trace_threadsafe[i]) - if len(edges_to_be_added_edge_rule_threadsafe[i]) > 0: - edges_to_be_added_edge_rule.extend(edges_to_be_added_edge_rule_threadsafe[i]) + if len(rules_to_be_applied_node_trace_threadsafe[ri]) > 0: + rules_to_be_applied_node_trace.extend(rules_to_be_applied_node_trace_threadsafe[ri]) + if len(rules_to_be_applied_edge_trace_threadsafe[ri]) > 0: + rules_to_be_applied_edge_trace.extend(rules_to_be_applied_edge_trace_threadsafe[ri]) + if len(edges_to_be_added_edge_rule_threadsafe[ri]) > 0: + edges_to_be_added_edge_rule.extend(edges_to_be_added_edge_rule_threadsafe[ri]) # Merge threadsafe flags for in_loop and update in_loop = in_loop update = update - for i in range(len(rules)): - if in_loop_threadsafe[i]: + for ri in range(len(rules)): + if in_loop_threadsafe[ri]: in_loop = True - if not update_threadsafe[i]: + if not update_threadsafe[ri]: update = False # Check for convergence after each timestep (perfect convergence or convergence specified by user) @@ -1423,14 +1423,18 @@ def get_rule_edge_clause_grounding(clause_var_1, clause_var_2, groundings, groun # We replace Y by the sources of Z elif clause_var_1 not in groundings and clause_var_2 in groundings: for n in groundings[clause_var_2]: - es = numba.typed.List([(nn, n) for nn in reverse_neighbors[n]]) + es = numba.typed.List.empty_list(edge_type) + for nn in reverse_neighbors[n]: + es.append((nn, n)) edge_groundings.extend(es) # Case 3: # We replace Z by the neighbors of Y elif clause_var_1 in groundings and clause_var_2 not in groundings: for n in groundings[clause_var_1]: - es = numba.typed.List([(n, nn) for nn in neighbors[n]]) + es = numba.typed.List.empty_list(edge_type) + for nn in neighbors[n]: + es.append((n, nn)) edge_groundings.extend(es) # Case 4: @@ -1443,7 +1447,10 @@ def get_rule_edge_clause_grounding(clause_var_1, clause_var_2, groundings, groun else: groundings_clause_var_2_set = set(groundings[clause_var_2]) for n in groundings[clause_var_1]: - es = numba.typed.List([(n, nn) for nn in neighbors[n] if nn in groundings_clause_var_2_set]) + es = numba.typed.List.empty_list(edge_type) + for nn in neighbors[n]: + if nn in groundings_clause_var_2_set: + es.append((n, nn)) edge_groundings.extend(es) return edge_groundings diff --git a/pyreason/scripts/interpretation/interpretation_fp.py b/pyreason/scripts/interpretation/interpretation_fp.py index 2cf174d0..fe03f7bf 100755 --- a/pyreason/scripts/interpretation/interpretation_fp.py +++ b/pyreason/scripts/interpretation/interpretation_fp.py @@ -360,9 +360,9 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi facts_to_be_applied_node_new.clear() facts_to_be_applied_node_trace_new.clear() nodes_set = set(nodes) - for i in range(len(facts_to_be_applied_node)): - if facts_to_be_applied_node[i][0] == t: - comp, l, bnd, static, graph_attribute = facts_to_be_applied_node[i][1], facts_to_be_applied_node[i][2], facts_to_be_applied_node[i][3], facts_to_be_applied_node[i][4], facts_to_be_applied_node[i][5] + for fi in range(len(facts_to_be_applied_node)): + if facts_to_be_applied_node[fi][0] == t: + comp, l, bnd, static, graph_attribute = facts_to_be_applied_node[fi][1], facts_to_be_applied_node[fi][2], facts_to_be_applied_node[fi][3], facts_to_be_applied_node[fi][4], facts_to_be_applied_node[fi][5] # If the component is not in the graph, add it if comp not in nodes_set: nodes_set.add(comp) @@ -375,27 +375,27 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi # Check if we should even store any of the changes to the rule trace etc. # Inverse of this is: if not save_graph_attributes_to_rule_trace and graph_attribute if (save_graph_attributes_to_rule_trace or not graph_attribute) and store_interpretation_changes: - meta_name = facts_to_be_applied_node_trace[i] if atom_trace else '' + meta_name = facts_to_be_applied_node_trace[fi] if atom_trace else '' rule_trace_node.append((numba.types.uint16(t), numba.types.uint16(fp_cnt), comp, l, bnd, True, 'Fact', meta_name, '')) if atom_trace: - _update_rule_trace(rule_trace_node_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), bnd, facts_to_be_applied_node_trace[i]) + _update_rule_trace(rule_trace_node_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), bnd, facts_to_be_applied_node_trace[fi]) for p1, p2 in ipl: if p1==l: rule_trace_node.append((numba.types.uint16(t), numba.types.uint16(fp_cnt), comp, p2, interpretations_node[t][comp].world[p2], True, 'IPL', f'IPL: {l.get_value()}', '')) if atom_trace: - _update_rule_trace(rule_trace_node_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), interpretations_node[t][comp].world[p2], facts_to_be_applied_node_trace[i]) + _update_rule_trace(rule_trace_node_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), interpretations_node[t][comp].world[p2], facts_to_be_applied_node_trace[fi]) elif p2==l: rule_trace_node.append((numba.types.uint16(t), numba.types.uint16(fp_cnt), comp, p1, interpretations_node[t][comp].world[p1], True, 'IPL', f'IPL: {l.get_value()}', '')) if atom_trace: - _update_rule_trace(rule_trace_node_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), interpretations_node[t][comp].world[p1], facts_to_be_applied_node_trace[i]) - + _update_rule_trace(rule_trace_node_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), interpretations_node[t][comp].world[p1], facts_to_be_applied_node_trace[fi]) + else: # Check for inconsistencies (multiple facts) if check_consistent_node(interpretations_node[t], comp, (l, bnd)): mode = 'graph-attribute-fact' if graph_attribute else 'fact' override = True if update_mode == 'override' else False - u, changes = _update_node(interpretations_node[t], predicate_map_node, comp, (l, bnd), ipl, rule_trace_node, fp_cnt, t, static, convergence_mode, atom_trace, save_graph_attributes_to_rule_trace, rules_to_be_applied_node_trace, i, facts_to_be_applied_node_trace, rule_trace_node_atoms, store_interpretation_changes, mode=mode, override=override) - + u, changes = _update_node(interpretations_node[t], predicate_map_node, comp, (l, bnd), ipl, rule_trace_node, fp_cnt, t, static, convergence_mode, atom_trace, save_graph_attributes_to_rule_trace, rules_to_be_applied_node_trace, fi, facts_to_be_applied_node_trace, rule_trace_node_atoms, store_interpretation_changes, mode=mode, override=override) + update = u or update if update: max_t_changes = max(max_t_changes, t) @@ -408,10 +408,10 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi else: mode = 'graph-attribute-fact' if graph_attribute else 'fact' if inconsistency_check: - resolve_inconsistency_node(interpretations_node[t], comp, (l, bnd), ipl, t, fp_cnt, i, atom_trace, rule_trace_node, rule_trace_node_atoms, rules_to_be_applied_node_trace, facts_to_be_applied_node_trace, store_interpretation_changes, mode=mode) + resolve_inconsistency_node(interpretations_node[t], comp, (l, bnd), ipl, t, fp_cnt, fi, atom_trace, rule_trace_node, rule_trace_node_atoms, rules_to_be_applied_node_trace, facts_to_be_applied_node_trace, store_interpretation_changes, mode=mode) else: - u, changes = _update_node(interpretations_node[t], predicate_map_node, comp, (l, bnd), ipl, rule_trace_node, fp_cnt, t, static, convergence_mode, atom_trace, save_graph_attributes_to_rule_trace, rules_to_be_applied_node_trace, i, facts_to_be_applied_node_trace, rule_trace_node_atoms, store_interpretation_changes, mode=mode, override=True) - + u, changes = _update_node(interpretations_node[t], predicate_map_node, comp, (l, bnd), ipl, rule_trace_node, fp_cnt, t, static, convergence_mode, atom_trace, save_graph_attributes_to_rule_trace, rules_to_be_applied_node_trace, fi, facts_to_be_applied_node_trace, rule_trace_node_atoms, store_interpretation_changes, mode=mode, override=True) + update = u or update if update: max_t_changes = max(max_t_changes, t) @@ -420,17 +420,17 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi bound_delta = max(bound_delta, changes) else: changes_cnt += changes - + if static: - facts_to_be_applied_node_new.append((numba.types.uint16(facts_to_be_applied_node[i][0]+1), comp, l, bnd, static, graph_attribute)) + facts_to_be_applied_node_new.append((numba.types.uint16(facts_to_be_applied_node[fi][0]+1), comp, l, bnd, static, graph_attribute)) if atom_trace: - facts_to_be_applied_node_trace_new.append(facts_to_be_applied_node_trace[i]) - + facts_to_be_applied_node_trace_new.append(facts_to_be_applied_node_trace[fi]) + # If time doesn't match, fact to be applied later else: - facts_to_be_applied_node_new.append(facts_to_be_applied_node[i]) + facts_to_be_applied_node_new.append(facts_to_be_applied_node[fi]) if atom_trace: - facts_to_be_applied_node_trace_new.append(facts_to_be_applied_node_trace[i]) + facts_to_be_applied_node_trace_new.append(facts_to_be_applied_node_trace[fi]) # Update list of facts with ones that have not been applied yet (delete applied facts) facts_to_be_applied_node[:] = facts_to_be_applied_node_new.copy() @@ -443,40 +443,40 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi facts_to_be_applied_edge_new.clear() facts_to_be_applied_edge_trace_new.clear() edges_set = set(edges) - for i in range(len(facts_to_be_applied_edge)): - if facts_to_be_applied_edge[i][0] == t: - comp, l, bnd, static, graph_attribute = facts_to_be_applied_edge[i][1], facts_to_be_applied_edge[i][2], facts_to_be_applied_edge[i][3], facts_to_be_applied_edge[i][4], facts_to_be_applied_edge[i][5] + for fi in range(len(facts_to_be_applied_edge)): + if facts_to_be_applied_edge[fi][0] == t: + comp, l, bnd, static, graph_attribute = facts_to_be_applied_edge[fi][1], facts_to_be_applied_edge[fi][2], facts_to_be_applied_edge[fi][3], facts_to_be_applied_edge[fi][4], facts_to_be_applied_edge[fi][5] # If the component is not in the graph, add it if comp not in edges_set: _add_edge(comp[0], comp[1], neighbors, reverse_neighbors, nodes, edges, label.Label(''), interpretations_node[t], interpretations_edge[t], predicate_map_edge, t) edges_set.add(comp) elif comp not in interpretations_edge[t]: _add_edge_to_interpretation(comp, interpretations_edge[t]) - + # Check if bnd is static. Then no need to update, just add to rule trace, check if graph attribute, and add ipl complement to rule trace as well if l in interpretations_edge[t][comp].world and interpretations_edge[t][comp].world[l].is_static(): # Inverse of this is: if not save_graph_attributes_to_rule_trace and graph_attribute if (save_graph_attributes_to_rule_trace or not graph_attribute) and store_interpretation_changes: - meta_name = facts_to_be_applied_edge_trace[i] if atom_trace else '' + meta_name = facts_to_be_applied_edge_trace[fi] if atom_trace else '' rule_trace_edge.append((numba.types.uint16(t), numba.types.uint16(fp_cnt), comp, l, interpretations_edge[t][comp].world[l], True, 'Fact', meta_name, '')) if atom_trace: - _update_rule_trace(rule_trace_edge_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), bnd, facts_to_be_applied_edge_trace[i]) + _update_rule_trace(rule_trace_edge_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), bnd, facts_to_be_applied_edge_trace[fi]) for p1, p2 in ipl: if p1 == l: rule_trace_edge.append((numba.types.uint16(t), numba.types.uint16(fp_cnt), comp, p2, interpretations_edge[t][comp].world[p2], True, 'IPL', f'IPL: {l.get_value()}', '')) if atom_trace: - _update_rule_trace(rule_trace_edge_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), interpretations_edge[t][comp].world[p2], facts_to_be_applied_edge_trace[i]) + _update_rule_trace(rule_trace_edge_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), interpretations_edge[t][comp].world[p2], facts_to_be_applied_edge_trace[fi]) elif p2 == l: rule_trace_edge.append((numba.types.uint16(t), numba.types.uint16(fp_cnt), comp, p1, interpretations_edge[t][comp].world[p1], True, 'IPL', f'IPL: {l.get_value()}', '')) if atom_trace: - _update_rule_trace(rule_trace_edge_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), interpretations_edge[t][comp].world[p1], facts_to_be_applied_edge_trace[i]) + _update_rule_trace(rule_trace_edge_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), interpretations_edge[t][comp].world[p1], facts_to_be_applied_edge_trace[fi]) else: # Check for inconsistencies if check_consistent_edge(interpretations_edge[t], comp, (l, bnd)): mode = 'graph-attribute-fact' if graph_attribute else 'fact' override = True if update_mode == 'override' else False - u, changes = _update_edge(interpretations_edge[t], predicate_map_edge, comp, (l, bnd), ipl, rule_trace_edge, fp_cnt, t, static, convergence_mode, atom_trace, save_graph_attributes_to_rule_trace, rules_to_be_applied_edge_trace, i, facts_to_be_applied_edge_trace, rule_trace_edge_atoms, store_interpretation_changes, mode=mode, override=override) - + u, changes = _update_edge(interpretations_edge[t], predicate_map_edge, comp, (l, bnd), ipl, rule_trace_edge, fp_cnt, t, static, convergence_mode, atom_trace, save_graph_attributes_to_rule_trace, rules_to_be_applied_edge_trace, fi, facts_to_be_applied_edge_trace, rule_trace_edge_atoms, store_interpretation_changes, mode=mode, override=override) + update = u or update if update: max_t_changes = max(max_t_changes, t) @@ -489,10 +489,10 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi else: mode = 'graph-attribute-fact' if graph_attribute else 'fact' if inconsistency_check: - resolve_inconsistency_edge(interpretations_edge[t], comp, (l, bnd), ipl, t, fp_cnt, i, atom_trace, rule_trace_edge, rule_trace_edge_atoms, rules_to_be_applied_edge_trace, facts_to_be_applied_edge_trace, store_interpretation_changes, mode=mode) + resolve_inconsistency_edge(interpretations_edge[t], comp, (l, bnd), ipl, t, fp_cnt, fi, atom_trace, rule_trace_edge, rule_trace_edge_atoms, rules_to_be_applied_edge_trace, facts_to_be_applied_edge_trace, store_interpretation_changes, mode=mode) else: - u, changes = _update_edge(interpretations_edge[t], predicate_map_edge, comp, (l, bnd), ipl, rule_trace_edge, fp_cnt, t, static, convergence_mode, atom_trace, save_graph_attributes_to_rule_trace, rules_to_be_applied_edge_trace, i, facts_to_be_applied_edge_trace, rule_trace_edge_atoms, store_interpretation_changes, mode=mode, override=True) - + u, changes = _update_edge(interpretations_edge[t], predicate_map_edge, comp, (l, bnd), ipl, rule_trace_edge, fp_cnt, t, static, convergence_mode, atom_trace, save_graph_attributes_to_rule_trace, rules_to_be_applied_edge_trace, fi, facts_to_be_applied_edge_trace, rule_trace_edge_atoms, store_interpretation_changes, mode=mode, override=True) + update = u or update if update: max_t_changes = max(max_t_changes, t) @@ -501,17 +501,17 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi bound_delta = max(bound_delta, changes) else: changes_cnt += changes - + if static: - facts_to_be_applied_edge_new.append((numba.types.uint16(facts_to_be_applied_edge[i][0]+1), comp, l, bnd, static, graph_attribute)) + facts_to_be_applied_edge_new.append((numba.types.uint16(facts_to_be_applied_edge[fi][0]+1), comp, l, bnd, static, graph_attribute)) if atom_trace: - facts_to_be_applied_edge_trace_new.append(facts_to_be_applied_edge_trace[i]) - + facts_to_be_applied_edge_trace_new.append(facts_to_be_applied_edge_trace[fi]) + # Time doesn't match, fact to be applied later else: - facts_to_be_applied_edge_new.append(facts_to_be_applied_edge[i]) + facts_to_be_applied_edge_new.append(facts_to_be_applied_edge[fi]) if atom_trace: - facts_to_be_applied_edge_trace_new.append(facts_to_be_applied_edge_trace[i]) + facts_to_be_applied_edge_trace_new.append(facts_to_be_applied_edge_trace[fi]) # Update list of facts with ones that have not been applied yet (delete applied facts) facts_to_be_applied_edge[:] = facts_to_be_applied_edge_new.copy() @@ -528,13 +528,13 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi rules_to_be_applied_edge_trace_threadsafe = numba.typed.List([numba.typed.List.empty_list(rules_to_be_applied_trace_type) for _ in range(len(rules))]) edges_to_be_added_edge_rule_threadsafe = numba.typed.List([numba.typed.List.empty_list(edges_to_be_added_type) for _ in range(len(rules))]) - for i in prange(len(rules)): - rule = rules[i] + for ri in prange(len(rules)): + rule = rules[ri] # Only go through if the rule can be applied within the given timesteps, or we're running until convergence delta_t = rule.get_delta() if t + delta_t <= tmax or tmax == -1 or again: - applicable_node_rules, applicable_edge_rules = _ground_rule(rule, interpretations_node[t], interpretations_edge[t], predicate_map_node, predicate_map_edge, nodes, edges, neighbors, reverse_neighbors, atom_trace, extended_ann_fn_flags[i], allow_ground_rules, t, head_functions, closed_world_predicates) + applicable_node_rules, applicable_edge_rules = _ground_rule(rule, interpretations_node[t], interpretations_edge[t], predicate_map_node, predicate_map_edge, nodes, edges, neighbors, reverse_neighbors, atom_trace, extended_ann_fn_flags[ri], allow_ground_rules, t, head_functions, closed_world_predicates) # Loop through applicable rules and add them to the rules to be applied for later or next fp operation for applicable_rule in applicable_node_rules: @@ -567,9 +567,9 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi bnd_u = min(max(bnd[1], 0), 1) bnd = interval.closed(bnd_l, bnd_u) max_rules_time = max(max_rules_time, t + delta_t) - rules_to_be_applied_node_threadsafe[i].append((numba.types.uint16(t + delta_t), n, rule.get_target(), bnd, rule.is_static_rule())) + rules_to_be_applied_node_threadsafe[ri].append((numba.types.uint16(t + delta_t), n, rule.get_target(), bnd, rule.is_static_rule())) if atom_trace: - rules_to_be_applied_node_trace_threadsafe[i].append((qualified_nodes, qualified_edges, rule.get_name())) + rules_to_be_applied_node_trace_threadsafe[ri].append((qualified_nodes, qualified_edges, rule.get_name())) # If delta_t is zero we apply the rules and check if more are applicable if delta_t == 0: @@ -610,29 +610,29 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi bnd = interval.closed(bnd_l, bnd_u) max_rules_time = max(max_rules_time, t+delta_t) # edges_to_be_added_edge_rule.append(edges_to_add) - edges_to_be_added_edge_rule_threadsafe[i].append(edges_to_add) - rules_to_be_applied_edge_threadsafe[i].append((numba.types.uint16(t+delta_t), e, rule.get_target(), bnd, rule.is_static_rule())) + edges_to_be_added_edge_rule_threadsafe[ri].append(edges_to_add) + rules_to_be_applied_edge_threadsafe[ri].append((numba.types.uint16(t+delta_t), e, rule.get_target(), bnd, rule.is_static_rule())) if atom_trace: # rules_to_be_applied_edge_trace.append((qualified_nodes, qualified_edges, rule.get_name())) - rules_to_be_applied_edge_trace_threadsafe[i].append((qualified_nodes, qualified_edges, rule.get_name())) + rules_to_be_applied_edge_trace_threadsafe[ri].append((qualified_nodes, qualified_edges, rule.get_name())) # If delta_t is zero we apply the rules and check if more are applicable if delta_t == 0: update = False # Update lists after parallel run - for i in range(len(rules)): - if len(rules_to_be_applied_node_threadsafe[i]) > 0: - rules_to_be_applied_node.extend(rules_to_be_applied_node_threadsafe[i]) - if len(rules_to_be_applied_edge_threadsafe[i]) > 0: - rules_to_be_applied_edge.extend(rules_to_be_applied_edge_threadsafe[i]) + for ri in range(len(rules)): + if len(rules_to_be_applied_node_threadsafe[ri]) > 0: + rules_to_be_applied_node.extend(rules_to_be_applied_node_threadsafe[ri]) + if len(rules_to_be_applied_edge_threadsafe[ri]) > 0: + rules_to_be_applied_edge.extend(rules_to_be_applied_edge_threadsafe[ri]) if atom_trace: - if len(rules_to_be_applied_node_trace_threadsafe[i]) > 0: - rules_to_be_applied_node_trace.extend(rules_to_be_applied_node_trace_threadsafe[i]) - if len(rules_to_be_applied_edge_trace_threadsafe[i]) > 0: - rules_to_be_applied_edge_trace.extend(rules_to_be_applied_edge_trace_threadsafe[i]) - if len(edges_to_be_added_edge_rule_threadsafe[i]) > 0: - edges_to_be_added_edge_rule.extend(edges_to_be_added_edge_rule_threadsafe[i]) + if len(rules_to_be_applied_node_trace_threadsafe[ri]) > 0: + rules_to_be_applied_node_trace.extend(rules_to_be_applied_node_trace_threadsafe[ri]) + if len(rules_to_be_applied_edge_trace_threadsafe[ri]) > 0: + rules_to_be_applied_edge_trace.extend(rules_to_be_applied_edge_trace_threadsafe[ri]) + if len(edges_to_be_added_edge_rule_threadsafe[ri]) > 0: + edges_to_be_added_edge_rule.extend(edges_to_be_added_edge_rule_threadsafe[ri]) # Increment t, update number of ground atoms t += 1 @@ -682,10 +682,10 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi rules_to_remove_idx.add(idx) # Remove from rules to be applied and edges to be applied lists after coming out from loop - rules_to_be_applied_node[:] = numba.typed.List([rules_to_be_applied_node[i] for i in range(len(rules_to_be_applied_node)) if i not in rules_to_remove_idx]) - edges_to_be_added_node_rule[:] = numba.typed.List([edges_to_be_added_node_rule[i] for i in range(len(edges_to_be_added_node_rule)) if i not in rules_to_remove_idx]) + rules_to_be_applied_node[:] = numba.typed.List([rules_to_be_applied_node[j] for j in range(len(rules_to_be_applied_node)) if j not in rules_to_remove_idx]) + edges_to_be_added_node_rule[:] = numba.typed.List([edges_to_be_added_node_rule[j] for j in range(len(edges_to_be_added_node_rule)) if j not in rules_to_remove_idx]) if atom_trace: - rules_to_be_applied_node_trace[:] = numba.typed.List([rules_to_be_applied_node_trace[i] for i in range(len(rules_to_be_applied_node_trace)) if i not in rules_to_remove_idx]) + rules_to_be_applied_node_trace[:] = numba.typed.List([rules_to_be_applied_node_trace[j] for j in range(len(rules_to_be_applied_node_trace)) if j not in rules_to_remove_idx]) # Edges rules_to_remove_idx.clear() @@ -766,10 +766,10 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi rules_to_remove_idx.add(idx) # Remove from rules to be applied and edges to be applied lists after coming out from loop - rules_to_be_applied_edge[:] = numba.typed.List([rules_to_be_applied_edge[i] for i in range(len(rules_to_be_applied_edge)) if i not in rules_to_remove_idx]) - edges_to_be_added_edge_rule[:] = numba.typed.List([edges_to_be_added_edge_rule[i] for i in range(len(edges_to_be_added_edge_rule)) if i not in rules_to_remove_idx]) + rules_to_be_applied_edge[:] = numba.typed.List([rules_to_be_applied_edge[j] for j in range(len(rules_to_be_applied_edge)) if j not in rules_to_remove_idx]) + edges_to_be_added_edge_rule[:] = numba.typed.List([edges_to_be_added_edge_rule[j] for j in range(len(edges_to_be_added_edge_rule)) if j not in rules_to_remove_idx]) if atom_trace: - rules_to_be_applied_edge_trace[:] = numba.typed.List([rules_to_be_applied_edge_trace[i] for i in range(len(rules_to_be_applied_edge_trace)) if i not in rules_to_remove_idx]) + rules_to_be_applied_edge_trace[:] = numba.typed.List([rules_to_be_applied_edge_trace[j] for j in range(len(rules_to_be_applied_edge_trace)) if j not in rules_to_remove_idx]) # Check for convergence after each timestep (perfect convergence or convergence specified by user) # Check number of changed interpretations or max bound change @@ -1543,14 +1543,18 @@ def get_rule_edge_clause_grounding(clause_var_1, clause_var_2, groundings, groun # We replace Y by the sources of Z elif clause_var_1 not in groundings and clause_var_2 in groundings: for n in groundings[clause_var_2]: - es = numba.typed.List([(nn, n) for nn in reverse_neighbors[n]]) + es = numba.typed.List.empty_list(edge_type) + for nn in reverse_neighbors[n]: + es.append((nn, n)) edge_groundings.extend(es) # Case 3: # We replace Z by the neighbors of Y elif clause_var_1 in groundings and clause_var_2 not in groundings: for n in groundings[clause_var_1]: - es = numba.typed.List([(n, nn) for nn in neighbors[n]]) + es = numba.typed.List.empty_list(edge_type) + for nn in neighbors[n]: + es.append((n, nn)) edge_groundings.extend(es) # Case 4: @@ -1563,7 +1567,10 @@ def get_rule_edge_clause_grounding(clause_var_1, clause_var_2, groundings, groun else: groundings_clause_var_2_set = set(groundings[clause_var_2]) for n in groundings[clause_var_1]: - es = numba.typed.List([(n, nn) for nn in neighbors[n] if nn in groundings_clause_var_2_set]) + es = numba.typed.List.empty_list(edge_type) + for nn in neighbors[n]: + if nn in groundings_clause_var_2_set: + es.append((n, nn)) edge_groundings.extend(es) return edge_groundings diff --git a/pyreason/scripts/interpretation/interpretation_parallel.py b/pyreason/scripts/interpretation/interpretation_parallel.py index 251713b8..190ab7a4 100644 --- a/pyreason/scripts/interpretation/interpretation_parallel.py +++ b/pyreason/scripts/interpretation/interpretation_parallel.py @@ -282,9 +282,9 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi facts_to_be_applied_node_new.clear() facts_to_be_applied_node_trace_new.clear() nodes_set = set(nodes) - for i in range(len(facts_to_be_applied_node)): - if facts_to_be_applied_node[i][0] == t: - comp, l, bnd, static, graph_attribute = facts_to_be_applied_node[i][1], facts_to_be_applied_node[i][2], facts_to_be_applied_node[i][3], facts_to_be_applied_node[i][4], facts_to_be_applied_node[i][5] + for fi in range(len(facts_to_be_applied_node)): + if facts_to_be_applied_node[fi][0] == t: + comp, l, bnd, static, graph_attribute = facts_to_be_applied_node[fi][1], facts_to_be_applied_node[fi][2], facts_to_be_applied_node[fi][3], facts_to_be_applied_node[fi][4], facts_to_be_applied_node[fi][5] # If the component is not in the graph, add it if comp not in nodes_set: _add_node(comp, neighbors, reverse_neighbors, nodes, interpretations_node) @@ -295,26 +295,26 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi # Check if we should even store any of the changes to the rule trace etc. # Inverse of this is: if not save_graph_attributes_to_rule_trace and graph_attribute if (save_graph_attributes_to_rule_trace or not graph_attribute) and store_interpretation_changes: - meta_name = facts_to_be_applied_node_trace[i] if atom_trace else '' + meta_name = facts_to_be_applied_node_trace[fi] if atom_trace else '' rule_trace_node.append((numba.types.uint16(t), numba.types.uint16(fp_cnt), comp, l, bnd, True, 'Fact', meta_name, '')) if atom_trace: - _update_rule_trace(rule_trace_node_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), bnd, facts_to_be_applied_node_trace[i]) + _update_rule_trace(rule_trace_node_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), bnd, facts_to_be_applied_node_trace[fi]) for p1, p2 in ipl: if p1==l: rule_trace_node.append((numba.types.uint16(t), numba.types.uint16(fp_cnt), comp, p2, interpretations_node[comp].world[p2], True, 'IPL', f'IPL: {l.get_value()}', '')) if atom_trace: - _update_rule_trace(rule_trace_node_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), interpretations_node[comp].world[p2], facts_to_be_applied_node_trace[i]) + _update_rule_trace(rule_trace_node_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), interpretations_node[comp].world[p2], facts_to_be_applied_node_trace[fi]) elif p2==l: rule_trace_node.append((numba.types.uint16(t), numba.types.uint16(fp_cnt), comp, p1, interpretations_node[comp].world[p1], True, 'IPL', f'IPL: {l.get_value()}', '')) if atom_trace: - _update_rule_trace(rule_trace_node_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), interpretations_node[comp].world[p1], facts_to_be_applied_node_trace[i]) + _update_rule_trace(rule_trace_node_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), interpretations_node[comp].world[p1], facts_to_be_applied_node_trace[fi]) else: # Check for inconsistencies (multiple facts) if check_consistent_node(interpretations_node, comp, (l, bnd)): mode = 'graph-attribute-fact' if graph_attribute else 'fact' override = True if update_mode == 'override' else False - u, changes = _update_node(interpretations_node, predicate_map_node, comp, (l, bnd), ipl, rule_trace_node, fp_cnt, t, static, convergence_mode, atom_trace, save_graph_attributes_to_rule_trace, rules_to_be_applied_node_trace, i, facts_to_be_applied_node_trace, rule_trace_node_atoms, store_interpretation_changes, num_ga, mode=mode, override=override) + u, changes = _update_node(interpretations_node, predicate_map_node, comp, (l, bnd), ipl, rule_trace_node, fp_cnt, t, static, convergence_mode, atom_trace, save_graph_attributes_to_rule_trace, rules_to_be_applied_node_trace, fi, facts_to_be_applied_node_trace, rule_trace_node_atoms, store_interpretation_changes, num_ga, mode=mode, override=override) update = u or update # Update convergence params @@ -326,9 +326,9 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi else: mode = 'graph-attribute-fact' if graph_attribute else 'fact' if inconsistency_check: - resolve_inconsistency_node(interpretations_node, comp, (l, bnd), ipl, t, fp_cnt, i, atom_trace, rule_trace_node, rule_trace_node_atoms, rules_to_be_applied_node_trace, facts_to_be_applied_node_trace, store_interpretation_changes, mode=mode) + resolve_inconsistency_node(interpretations_node, comp, (l, bnd), ipl, t, fp_cnt, fi, atom_trace, rule_trace_node, rule_trace_node_atoms, rules_to_be_applied_node_trace, facts_to_be_applied_node_trace, store_interpretation_changes, mode=mode) else: - u, changes = _update_node(interpretations_node, predicate_map_node, comp, (l, bnd), ipl, rule_trace_node, fp_cnt, t, static, convergence_mode, atom_trace, save_graph_attributes_to_rule_trace, rules_to_be_applied_node_trace, i, facts_to_be_applied_node_trace, rule_trace_node_atoms, store_interpretation_changes, num_ga, mode=mode, override=True) + u, changes = _update_node(interpretations_node, predicate_map_node, comp, (l, bnd), ipl, rule_trace_node, fp_cnt, t, static, convergence_mode, atom_trace, save_graph_attributes_to_rule_trace, rules_to_be_applied_node_trace, fi, facts_to_be_applied_node_trace, rule_trace_node_atoms, store_interpretation_changes, num_ga, mode=mode, override=True) update = u or update # Update convergence params @@ -338,15 +338,15 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi changes_cnt += changes if static: - facts_to_be_applied_node_new.append((numba.types.uint16(facts_to_be_applied_node[i][0]+1), comp, l, bnd, static, graph_attribute)) + facts_to_be_applied_node_new.append((numba.types.uint16(facts_to_be_applied_node[fi][0]+1), comp, l, bnd, static, graph_attribute)) if atom_trace: - facts_to_be_applied_node_trace_new.append(facts_to_be_applied_node_trace[i]) + facts_to_be_applied_node_trace_new.append(facts_to_be_applied_node_trace[fi]) # If time doesn't match, fact to be applied later else: - facts_to_be_applied_node_new.append(facts_to_be_applied_node[i]) + facts_to_be_applied_node_new.append(facts_to_be_applied_node[fi]) if atom_trace: - facts_to_be_applied_node_trace_new.append(facts_to_be_applied_node_trace[i]) + facts_to_be_applied_node_trace_new.append(facts_to_be_applied_node_trace[fi]) # Update list of facts with ones that have not been applied yet (delete applied facts) facts_to_be_applied_node[:] = facts_to_be_applied_node_new.copy() @@ -359,9 +359,9 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi facts_to_be_applied_edge_new.clear() facts_to_be_applied_edge_trace_new.clear() edges_set = set(edges) - for i in range(len(facts_to_be_applied_edge)): - if facts_to_be_applied_edge[i][0]==t: - comp, l, bnd, static, graph_attribute = facts_to_be_applied_edge[i][1], facts_to_be_applied_edge[i][2], facts_to_be_applied_edge[i][3], facts_to_be_applied_edge[i][4], facts_to_be_applied_edge[i][5] + for fi in range(len(facts_to_be_applied_edge)): + if facts_to_be_applied_edge[fi][0]==t: + comp, l, bnd, static, graph_attribute = facts_to_be_applied_edge[fi][1], facts_to_be_applied_edge[fi][2], facts_to_be_applied_edge[fi][3], facts_to_be_applied_edge[fi][4], facts_to_be_applied_edge[fi][5] # If the component is not in the graph, add it if comp not in edges_set: _add_edge(comp[0], comp[1], neighbors, reverse_neighbors, nodes, edges, label.Label(''), interpretations_node, interpretations_edge, predicate_map_edge, num_ga, t) @@ -371,25 +371,25 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi if l in interpretations_edge[comp].world and interpretations_edge[comp].world[l].is_static(): # Inverse of this is: if not save_graph_attributes_to_rule_trace and graph_attribute if (save_graph_attributes_to_rule_trace or not graph_attribute) and store_interpretation_changes: - meta_name = facts_to_be_applied_edge_trace[i] if atom_trace else '' + meta_name = facts_to_be_applied_edge_trace[fi] if atom_trace else '' rule_trace_edge.append((numba.types.uint16(t), numba.types.uint16(fp_cnt), comp, l, interpretations_edge[comp].world[l], True, 'Fact', meta_name, '')) if atom_trace: - _update_rule_trace(rule_trace_edge_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), bnd, facts_to_be_applied_edge_trace[i]) + _update_rule_trace(rule_trace_edge_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), bnd, facts_to_be_applied_edge_trace[fi]) for p1, p2 in ipl: if p1==l: rule_trace_edge.append((numba.types.uint16(t), numba.types.uint16(fp_cnt), comp, p2, interpretations_edge[comp].world[p2], True, 'IPL', f'IPL: {l.get_value()}', '')) if atom_trace: - _update_rule_trace(rule_trace_edge_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), interpretations_edge[comp].world[p2], facts_to_be_applied_edge_trace[i]) + _update_rule_trace(rule_trace_edge_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), interpretations_edge[comp].world[p2], facts_to_be_applied_edge_trace[fi]) elif p2==l: rule_trace_edge.append((numba.types.uint16(t), numba.types.uint16(fp_cnt), comp, p1, interpretations_edge[comp].world[p1], True, 'IPL', f'IPL: {l.get_value()}', '')) if atom_trace: - _update_rule_trace(rule_trace_edge_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), interpretations_edge[comp].world[p1], facts_to_be_applied_edge_trace[i]) + _update_rule_trace(rule_trace_edge_atoms, numba.typed.List.empty_list(numba.typed.List.empty_list(node_type)), numba.typed.List.empty_list(numba.typed.List.empty_list(edge_type)), interpretations_edge[comp].world[p1], facts_to_be_applied_edge_trace[fi]) else: # Check for inconsistencies if check_consistent_edge(interpretations_edge, comp, (l, bnd)): mode = 'graph-attribute-fact' if graph_attribute else 'fact' override = True if update_mode == 'override' else False - u, changes = _update_edge(interpretations_edge, predicate_map_edge, comp, (l, bnd), ipl, rule_trace_edge, fp_cnt, t, static, convergence_mode, atom_trace, save_graph_attributes_to_rule_trace, rules_to_be_applied_edge_trace, i, facts_to_be_applied_edge_trace, rule_trace_edge_atoms, store_interpretation_changes, num_ga, mode=mode, override=override) + u, changes = _update_edge(interpretations_edge, predicate_map_edge, comp, (l, bnd), ipl, rule_trace_edge, fp_cnt, t, static, convergence_mode, atom_trace, save_graph_attributes_to_rule_trace, rules_to_be_applied_edge_trace, fi, facts_to_be_applied_edge_trace, rule_trace_edge_atoms, store_interpretation_changes, num_ga, mode=mode, override=override) update = u or update # Update convergence params @@ -401,9 +401,9 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi else: mode = 'graph-attribute-fact' if graph_attribute else 'fact' if inconsistency_check: - resolve_inconsistency_edge(interpretations_edge, comp, (l, bnd), ipl, t, fp_cnt, i, atom_trace, rule_trace_edge, rule_trace_edge_atoms, rules_to_be_applied_edge_trace, facts_to_be_applied_edge_trace, store_interpretation_changes, mode=mode) + resolve_inconsistency_edge(interpretations_edge, comp, (l, bnd), ipl, t, fp_cnt, fi, atom_trace, rule_trace_edge, rule_trace_edge_atoms, rules_to_be_applied_edge_trace, facts_to_be_applied_edge_trace, store_interpretation_changes, mode=mode) else: - u, changes = _update_edge(interpretations_edge, predicate_map_edge, comp, (l, bnd), ipl, rule_trace_edge, fp_cnt, t, static, convergence_mode, atom_trace, save_graph_attributes_to_rule_trace, rules_to_be_applied_edge_trace, i, facts_to_be_applied_edge_trace, rule_trace_edge_atoms, store_interpretation_changes, num_ga, mode=mode, override=True) + u, changes = _update_edge(interpretations_edge, predicate_map_edge, comp, (l, bnd), ipl, rule_trace_edge, fp_cnt, t, static, convergence_mode, atom_trace, save_graph_attributes_to_rule_trace, rules_to_be_applied_edge_trace, fi, facts_to_be_applied_edge_trace, rule_trace_edge_atoms, store_interpretation_changes, num_ga, mode=mode, override=True) update = u or update # Update convergence params @@ -413,15 +413,15 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi changes_cnt += changes if static: - facts_to_be_applied_edge_new.append((numba.types.uint16(facts_to_be_applied_edge[i][0]+1), comp, l, bnd, static, graph_attribute)) + facts_to_be_applied_edge_new.append((numba.types.uint16(facts_to_be_applied_edge[fi][0]+1), comp, l, bnd, static, graph_attribute)) if atom_trace: - facts_to_be_applied_edge_trace_new.append(facts_to_be_applied_edge_trace[i]) + facts_to_be_applied_edge_trace_new.append(facts_to_be_applied_edge_trace[fi]) # Time doesn't match, fact to be applied later else: - facts_to_be_applied_edge_new.append(facts_to_be_applied_edge[i]) + facts_to_be_applied_edge_new.append(facts_to_be_applied_edge[fi]) if atom_trace: - facts_to_be_applied_edge_trace_new.append(facts_to_be_applied_edge_trace[i]) + facts_to_be_applied_edge_trace_new.append(facts_to_be_applied_edge_trace[fi]) # Update list of facts with ones that have not been applied yet (delete applied facts) facts_to_be_applied_edge[:] = facts_to_be_applied_edge_new.copy() @@ -470,10 +470,10 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi rules_to_remove_idx.add(idx) # Remove from rules to be applied and edges to be applied lists after coming out from loop - rules_to_be_applied_node[:] = numba.typed.List([rules_to_be_applied_node[i] for i in range(len(rules_to_be_applied_node)) if i not in rules_to_remove_idx]) - edges_to_be_added_node_rule[:] = numba.typed.List([edges_to_be_added_node_rule[i] for i in range(len(edges_to_be_added_node_rule)) if i not in rules_to_remove_idx]) + rules_to_be_applied_node[:] = numba.typed.List([rules_to_be_applied_node[j] for j in range(len(rules_to_be_applied_node)) if j not in rules_to_remove_idx]) + edges_to_be_added_node_rule[:] = numba.typed.List([edges_to_be_added_node_rule[j] for j in range(len(edges_to_be_added_node_rule)) if j not in rules_to_remove_idx]) if atom_trace: - rules_to_be_applied_node_trace[:] = numba.typed.List([rules_to_be_applied_node_trace[i] for i in range(len(rules_to_be_applied_node_trace)) if i not in rules_to_remove_idx]) + rules_to_be_applied_node_trace[:] = numba.typed.List([rules_to_be_applied_node_trace[j] for j in range(len(rules_to_be_applied_node_trace)) if j not in rules_to_remove_idx]) # Edges rules_to_remove_idx.clear() @@ -544,10 +544,10 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi rules_to_remove_idx.add(idx) # Remove from rules to be applied and edges to be applied lists after coming out from loop - rules_to_be_applied_edge[:] = numba.typed.List([rules_to_be_applied_edge[i] for i in range(len(rules_to_be_applied_edge)) if i not in rules_to_remove_idx]) - edges_to_be_added_edge_rule[:] = numba.typed.List([edges_to_be_added_edge_rule[i] for i in range(len(edges_to_be_added_edge_rule)) if i not in rules_to_remove_idx]) + rules_to_be_applied_edge[:] = numba.typed.List([rules_to_be_applied_edge[j] for j in range(len(rules_to_be_applied_edge)) if j not in rules_to_remove_idx]) + edges_to_be_added_edge_rule[:] = numba.typed.List([edges_to_be_added_edge_rule[j] for j in range(len(edges_to_be_added_edge_rule)) if j not in rules_to_remove_idx]) if atom_trace: - rules_to_be_applied_edge_trace[:] = numba.typed.List([rules_to_be_applied_edge_trace[i] for i in range(len(rules_to_be_applied_edge_trace)) if i not in rules_to_remove_idx]) + rules_to_be_applied_edge_trace[:] = numba.typed.List([rules_to_be_applied_edge_trace[j] for j in range(len(rules_to_be_applied_edge_trace)) if j not in rules_to_remove_idx]) # Fixed point if update: @@ -568,13 +568,13 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi in_loop_threadsafe.append(False) update_threadsafe.append(True) - for i in prange(len(rules)): - rule = rules[i] + for ri in prange(len(rules)): + rule = rules[ri] # Only go through if the rule can be applied within the given timesteps, or we're running until convergence delta_t = rule.get_delta() if t + delta_t <= tmax or tmax == -1 or again: - applicable_node_rules, applicable_edge_rules = _ground_rule(rule, interpretations_node, interpretations_edge, predicate_map_node, predicate_map_edge, nodes, edges, neighbors, reverse_neighbors, atom_trace, extended_ann_fn_flags[i], allow_ground_rules, num_ga, t, head_functions, closed_world_predicates) + applicable_node_rules, applicable_edge_rules = _ground_rule(rule, interpretations_node, interpretations_edge, predicate_map_node, predicate_map_edge, nodes, edges, neighbors, reverse_neighbors, atom_trace, extended_ann_fn_flags[ri], allow_ground_rules, num_ga, t, head_functions, closed_world_predicates) # Loop through applicable rules and add them to the rules to be applied for later or next fp operation for applicable_rule in applicable_node_rules: @@ -592,14 +592,14 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi bnd_u = min(max(bnd[1], 0), 1) bnd = interval.closed(bnd_l, bnd_u) max_rules_time = max(max_rules_time, t + delta_t) - rules_to_be_applied_node_threadsafe[i].append((numba.types.uint16(t + delta_t), n, rule.get_target(), bnd, rule.is_static_rule())) + rules_to_be_applied_node_threadsafe[ri].append((numba.types.uint16(t + delta_t), n, rule.get_target(), bnd, rule.is_static_rule())) if atom_trace: - rules_to_be_applied_node_trace_threadsafe[i].append((qualified_nodes, qualified_edges, rule.get_name())) + rules_to_be_applied_node_trace_threadsafe[ri].append((qualified_nodes, qualified_edges, rule.get_name())) # If delta_t is zero we apply the rules and check if more are applicable if delta_t == 0: - in_loop_threadsafe[i] = True - update_threadsafe[i] = False + in_loop_threadsafe[ri] = True + update_threadsafe[ri] = False for applicable_rule in applicable_edge_rules: e, annotations, qualified_nodes, qualified_edges, edges_to_add, clause_labels, clause_variables = applicable_rule @@ -617,38 +617,38 @@ def reason(interpretations_node, interpretations_edge, predicate_map_node, predi bnd = interval.closed(bnd_l, bnd_u) max_rules_time = max(max_rules_time, t+delta_t) # edges_to_be_added_edge_rule.append(edges_to_add) - edges_to_be_added_edge_rule_threadsafe[i].append(edges_to_add) - rules_to_be_applied_edge_threadsafe[i].append((numba.types.uint16(t+delta_t), e, rule.get_target(), bnd, rule.is_static_rule())) + edges_to_be_added_edge_rule_threadsafe[ri].append(edges_to_add) + rules_to_be_applied_edge_threadsafe[ri].append((numba.types.uint16(t+delta_t), e, rule.get_target(), bnd, rule.is_static_rule())) if atom_trace: # rules_to_be_applied_edge_trace.append((qualified_nodes, qualified_edges, rule.get_name())) - rules_to_be_applied_edge_trace_threadsafe[i].append((qualified_nodes, qualified_edges, rule.get_name())) + rules_to_be_applied_edge_trace_threadsafe[ri].append((qualified_nodes, qualified_edges, rule.get_name())) # If delta_t is zero we apply the rules and check if more are applicable if delta_t == 0: - in_loop_threadsafe[i] = True - update_threadsafe[i] = False + in_loop_threadsafe[ri] = True + update_threadsafe[ri] = False # Update lists after parallel run - for i in range(len(rules)): - if len(rules_to_be_applied_node_threadsafe[i]) > 0: - rules_to_be_applied_node.extend(rules_to_be_applied_node_threadsafe[i]) - if len(rules_to_be_applied_edge_threadsafe[i]) > 0: - rules_to_be_applied_edge.extend(rules_to_be_applied_edge_threadsafe[i]) + for ri in range(len(rules)): + if len(rules_to_be_applied_node_threadsafe[ri]) > 0: + rules_to_be_applied_node.extend(rules_to_be_applied_node_threadsafe[ri]) + if len(rules_to_be_applied_edge_threadsafe[ri]) > 0: + rules_to_be_applied_edge.extend(rules_to_be_applied_edge_threadsafe[ri]) if atom_trace: - if len(rules_to_be_applied_node_trace_threadsafe[i]) > 0: - rules_to_be_applied_node_trace.extend(rules_to_be_applied_node_trace_threadsafe[i]) - if len(rules_to_be_applied_edge_trace_threadsafe[i]) > 0: - rules_to_be_applied_edge_trace.extend(rules_to_be_applied_edge_trace_threadsafe[i]) - if len(edges_to_be_added_edge_rule_threadsafe[i]) > 0: - edges_to_be_added_edge_rule.extend(edges_to_be_added_edge_rule_threadsafe[i]) + if len(rules_to_be_applied_node_trace_threadsafe[ri]) > 0: + rules_to_be_applied_node_trace.extend(rules_to_be_applied_node_trace_threadsafe[ri]) + if len(rules_to_be_applied_edge_trace_threadsafe[ri]) > 0: + rules_to_be_applied_edge_trace.extend(rules_to_be_applied_edge_trace_threadsafe[ri]) + if len(edges_to_be_added_edge_rule_threadsafe[ri]) > 0: + edges_to_be_added_edge_rule.extend(edges_to_be_added_edge_rule_threadsafe[ri]) # Merge threadsafe flags for in_loop and update in_loop = in_loop update = update - for i in range(len(rules)): - if in_loop_threadsafe[i]: + for ri in range(len(rules)): + if in_loop_threadsafe[ri]: in_loop = True - if not update_threadsafe[i]: + if not update_threadsafe[ri]: update = False # Check for convergence after each timestep (perfect convergence or convergence specified by user) @@ -1423,14 +1423,18 @@ def get_rule_edge_clause_grounding(clause_var_1, clause_var_2, groundings, groun # We replace Y by the sources of Z elif clause_var_1 not in groundings and clause_var_2 in groundings: for n in groundings[clause_var_2]: - es = numba.typed.List([(nn, n) for nn in reverse_neighbors[n]]) + es = numba.typed.List.empty_list(edge_type) + for nn in reverse_neighbors[n]: + es.append((nn, n)) edge_groundings.extend(es) # Case 3: # We replace Z by the neighbors of Y elif clause_var_1 in groundings and clause_var_2 not in groundings: for n in groundings[clause_var_1]: - es = numba.typed.List([(n, nn) for nn in neighbors[n]]) + es = numba.typed.List.empty_list(edge_type) + for nn in neighbors[n]: + es.append((n, nn)) edge_groundings.extend(es) # Case 4: @@ -1443,7 +1447,10 @@ def get_rule_edge_clause_grounding(clause_var_1, clause_var_2, groundings, groun else: groundings_clause_var_2_set = set(groundings[clause_var_2]) for n in groundings[clause_var_1]: - es = numba.typed.List([(n, nn) for nn in neighbors[n] if nn in groundings_clause_var_2_set]) + es = numba.typed.List.empty_list(edge_type) + for nn in neighbors[n]: + if nn in groundings_clause_var_2_set: + es.append((n, nn)) edge_groundings.extend(es) return edge_groundings diff --git a/pytest.ini b/pytest.ini index ae7474ce..a026c659 100644 --- a/pytest.ini +++ b/pytest.ini @@ -33,8 +33,6 @@ markers = # Warnings configuration filterwarnings = - # Ignore specific warnings that are expected in test environment - ignore::UserWarning ignore::DeprecationWarning:numba.* ignore::FutureWarning:numba.* ignore::RuntimeWarning:numba.* diff --git a/requirements.txt b/requirements.txt index aabec6b4..5593480f 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,17 +1,20 @@ -networkx -pyyaml -pandas -numba==0.59.1 -numpy==1.26.4 +# Install all development dependencies: +# uv pip install -e ".[dev,torch,docs]" (recommended) +# pip install -r requirements.txt (legacy fallback) + +networkx>=3.1 +pyyaml>=6.0 +pandas>=2.0.0 +numba>=0.65.1 +numpy>=2.1,<2.5 memory_profiler +torch>=2.6.0 pytest -torch -setuptools_scm pytest-cov pre-commit +ruff sphinx_rtd_theme sphinx sphinx-autopackagesummary sphinx-autoapi -ruff diff --git a/setup.py b/setup.py deleted file mode 100644 index 54f08ef2..00000000 --- a/setup.py +++ /dev/null @@ -1,42 +0,0 @@ -from setuptools import setup, find_packages - -# Read the contents of README file -from pathlib import Path - -this_directory = Path(__file__).parent -long_description = (this_directory / "README.md").read_text(encoding='UTF-8') - -setup( - name='pyreason', - version='3.6.0', - author='Dyuman Aditya', - author_email='dyuman.aditya@gmail.com', - description='An explainable inference software supporting annotated, real valued, graph based and temporal logic', - long_description=long_description, - long_description_content_type='text/markdown', - url='https://github.com/lab-v2/pyreason', - license='BSD 3-clause', - project_urls={ - 'Bug Tracker': 'https://github.com/lab-v2/pyreason/issues', - 'Repository': 'https://github.com/lab-v2/pyreason' - }, - classifiers=[ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: BSD License", - "Operating System :: OS Independent" - ], - python_requires='>3.6', - install_requires=[ - 'networkx', - 'pyyaml', - 'pandas', - 'numba', - 'numpy', - 'memory_profiler', - 'pytest' - ], - use_scm_version=True, - setup_requires=['setuptools_scm'], - packages=find_packages(), - include_package_data=True -)