From a852ccdb809733625121a9e298dca65f15d3f91a Mon Sep 17 00:00:00 2001 From: Lars Malmqvist <12750146+lmlearning@users.noreply.github.com> Date: Fri, 25 Sep 2026 14:03:55 +0200 Subject: [PATCH] Provide a tested CPU example and reliable library imports --- .github/workflows/tests.yml | 16 +++++++ AFGCNv2/README | 27 ++++------- CITATION.cff | 10 ++++ GraphLib/dglutil.py | 8 +++- GraphLib/inference.py | 14 +++--- GraphLib/util.py | 5 +- README.md | 91 +++++++++++++++++++++++-------------- examples/gcn_demo.py | 41 +++++++++++++++++ requirements-cpu.txt | 7 +++ tests/test_cpu_model.py | 29 ++++++++++++ 10 files changed, 188 insertions(+), 60 deletions(-) create mode 100644 CITATION.cff create mode 100644 examples/gcn_demo.py create mode 100644 requirements-cpu.txt create mode 100644 tests/test_cpu_model.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6078d16..b86b0d2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,3 +20,19 @@ jobs: python-version: '3.11' - run: python -m pip install pytest - run: python -m pytest -q tests + + cpu-smoke: + runs-on: ubuntu-latest + env: + DGLBACKEND: pytorch + OMP_NUM_THREADS: '1' + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: '3.11' + - run: python -m pip install -r requirements-cpu.txt pytest + - run: python -m pytest -q tests + - run: python -m examples.gcn_demo diff --git a/AFGCNv2/README b/AFGCNv2/README index 966ee8e..fb02509 100644 --- a/AFGCNv2/README +++ b/AFGCNv2/README @@ -1,21 +1,12 @@ -The AFGCNv2 Solver is based on Python3. There are two major dependencies that must be installed prior to running the solver: pytorch, the deep learning engine used, and dgl, the graph library used. +AFGCNv2 is a historical competition-solver snapshot. -The solver is submitted to the approximate track and supports all the decision problems in that track (DC-PR, DS-PR, DC-ST, DS-ST, DC-CO, DS-CO, DC-SST, DS-SST, DC-STG, DS-STG, DS-ID). +This directory preserves solver.py, solver.sh, semantic-specific checkpoints, +thresholds and the accompanying paper. Its original environment is not the +root repository's CPU demo environment. -To run the solver please follow the following steps: +For the current documented and tested decision-solver workflow, use: +https://github.com/lmlearning/AFGCN -Install python3 if not installed. If the pip package manager is not installed, please install that as well. - -Then to install the pre-requiste python libraries, please run the following commands: -pip install dgl -f https://data.dgl.ai/wheels/repo.html -pip install dglgo -f https://data.dgl.ai/wheels-test/repo.html -pip install torch -pip install scikit-learn - -Once the pre-requisites have been put in place the solver can be called in the following manner, conforming to the solver requirements set out in the ICCMA 2023 call for solvers: - -./solver.sh -p -f -a - -Example: - -./solver.sh -p DS-ST -f myFile.tgf -a 2 \ No newline at end of file +For a self-contained GCN training example in this repository, see the root +README and examples/gcn_demo.py. Historical checkpoints should be interpreted +with the code, input format and configuration that produced them. diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..f0c4908 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,10 @@ +cff-version: 1.2.0 +message: "Please cite this software and the associated paper where applicable." +type: software +title: "AFGraphLib: Graph Learning for Abstract Argumentation" +authors: + - family-names: Malmqvist + given-names: Lars +repository-code: "https://github.com/lmlearning/AFGraphLib" +url: "https://github.com/lmlearning/AFGraphLib" +license: MIT diff --git a/GraphLib/dglutil.py b/GraphLib/dglutil.py index 7b6d037..d4af704 100644 --- a/GraphLib/dglutil.py +++ b/GraphLib/dglutil.py @@ -11,9 +11,13 @@ from dgl import DGLGraph import networkx as nx from dgl.nn.pytorch import GraphConv -from model import GCN import numpy as np -from util import parseTGF,parseAPX, get_features, read_solution_file, get_credulous_labels, get_sceptical_labels, get_masks +if __package__: + from .model import GCN + from .util import parseTGF, parseAPX, get_features, read_solution_file, get_credulous_labels, get_sceptical_labels, get_masks +else: + from model import GCN + from util import parseTGF, parseAPX, get_features, read_solution_file, get_credulous_labels, get_sceptical_labels, get_masks def load_graph(file_path, cutoff = 10000000, format="tgf"): diff --git a/GraphLib/inference.py b/GraphLib/inference.py index b9d2db7..dcc4d97 100644 --- a/GraphLib/inference.py +++ b/GraphLib/inference.py @@ -5,9 +5,14 @@ import torch.nn.functional as F from dgl import DGLGraph import networkx as nx -from dglutil import make_dgl_graph, merge_graphs, send_graph_to_device, load_graph -from util import parseTGF,get_features, read_solution_file, get_credulous_labels, get_masks,getRandomBatch, load_ckp -from model import GCN +if __package__: + from .dglutil import make_dgl_graph, merge_graphs, send_graph_to_device, load_graph + from .util import parseTGF, get_features, read_solution_file, get_credulous_labels, get_masks, getRandomBatch, load_ckp + from .model import GCN +else: + from dglutil import make_dgl_graph, merge_graphs, send_graph_to_device, load_graph + from util import parseTGF, get_features, read_solution_file, get_credulous_labels, get_masks, getRandomBatch, load_ckp + from model import GCN import argparse import pickle import numpy as np @@ -187,6 +192,3 @@ def detect_admbuster(nx_graph): return False else: return True - - - \ No newline at end of file diff --git a/GraphLib/util.py b/GraphLib/util.py index 82d8212..4ab5695 100644 --- a/GraphLib/util.py +++ b/GraphLib/util.py @@ -6,7 +6,10 @@ from dgl import DGLGraph import networkx as nx from dgl.nn.pytorch import GraphConv -from model import GCN +if __package__: + from .model import GCN +else: + from model import GCN import numpy as np import shutil diff --git a/README.md b/README.md index 5e0f57a..04fd758 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,63 @@ -# AFGraphLib: Graph Learning for Abstract Argumentation - -Graph learning tools and research materials for **abstract argumentation**: representing arguments and attacks as graphs, then learning to predict argument acceptance. - -## Explore the repository - -| Path | Purpose | -| --- | --- | -| [GraphLib](GraphLib/) | Graph utilities, models and inference code. | -| [AFs](AFs/) | Example argumentation frameworks and associated solutions. | -| [AFGCNv2](AFGCNv2/) | Solver implementation, checkpoints and accompanying research material. | -| [AFGCN_new.py](AFGCN_new.py) | GCN experiment implementation. | -| [pyg_train.py](pyg_train.py) | Graph-learning training script. | - -Start with the [model code](GraphLib/model.py) to inspect the architecture or the [solver documentation](AFGCNv2/README) to explore solving. Training scripts are research entry points; review their imports, data paths and configuration before running them. - -## Related projects - -- [AFGCN](https://github.com/lmlearning/AFGCN): dedicated solver and training repository. -- [FastAFGCN](https://github.com/lmlearning/FastAFGCN): quantized ONNX inference. -- [AFSubsample](https://github.com/lmlearning/AFSubsample): framework subsampling and analysis. - -## Component tests +# AFGraphLib: Graph Learning for Abstract Argumentation + +[![Tests](https://github.com/lmlearning/AFGraphLib/actions/workflows/tests.yml/badge.svg)](https://github.com/lmlearning/AFGraphLib/actions/workflows/tests.yml) + +Graph-learning components and experimental materials for **predicting which arguments are accepted in a network of arguments and attacks**. The core contains DGL graph construction, acceptance-label utilities, GCN models and inference experiments. + +**Start with the runnable GCN example below.** For the pretrained decision-solver workflow, see [AFGCN](https://github.com/lmlearning/AFGCN). + +## Run a CPU example + +Use Python 3.11 in an activated virtual environment, from the repository root. The pinned CPU environment supports Linux and Windows. ```bash -python -m pip install pytest -python -m pytest tests +python -m pip install -r requirements-cpu.txt pytest +python -m examples.gcn_demo +python -m pytest -q tests ``` -The device-transfer helper is isolated in `GraphLib/device.py` and remains available -through `GraphLib.dglutil`. It uses the requested device and keeps the original -attribute if its transfer fails. The graph is modified in place; earlier successful -transfers are not rolled back. These tests use graph/tensor protocol doubles and do -not require DGL or validate GPU execution or model training. +The example fits the existing `GraphLib.model.GCN` to an illustrative four-argument framework: `a → b → c`, plus an isolated argument `d`. It prints a `[4, 1]` logits shape and a decreasing training loss. This is a small training demonstration with known labels, not a held-out accuracy result. No corpus or checkpoint download is required. + +## Architecture and code map + +```mermaid +flowchart LR + A[Arguments and directed attacks] --> G[DGL graph and node features] + G --> M[GraphConv layers and dropout] + M --> L[Per-argument logits] + L --> T[Task-specific acceptance decisions] +``` + +| Path | What to inspect | +| --- | --- | +| [examples/gcn_demo.py](examples/gcn_demo.py) | A complete graph → model → loss → backward-pass example. | +| [GraphLib/model.py](GraphLib/model.py) | Graph convolutional model definitions. | +| [GraphLib/dglutil.py](GraphLib/dglutil.py) | Graph construction and batching helpers. | +| [GraphLib/util.py](GraphLib/util.py) | Framework parsing and acceptance-label utilities. | +| [GraphLib/inference.py](GraphLib/inference.py) | Grounded reasoning and neural-inference experiments. | +| [AFs](AFs/) | Research frameworks and solution files, stored with Git LFS. | +| [AFGCNv2](AFGCNv2/) | Historical competition solver snapshot and its paper. | + +The library modules support package imports from the repository root and the original script-style imports from inside `GraphLib`. Older standalone experiment scripts retain their original paths and configurations; the CPU example is the maintained first-run workflow. + +## Research context + +See [Approximating Problems in Abstract Argumentation with Graph Convolutional Networks](https://www-users.york.ac.uk/peter.nightingale/aij-argumentation-2024.pdf), by Lars Malmqvist, Tangming Yuan and Peter Nightingale, for the research approach and experimental evaluation. A smoke run here does not reproduce those experiments. + +Use [CITATION.cff](CITATION.cff) to cite this software, and cite the paper separately when discussing its findings. + +## Data and validation + +The demo runs without Git LFS. For the research corpus, install Git LFS and fetch the required `AFs/` paths; text files beginning with `version https://git-lfs.github.com/spec/v1` are pointers, not graph data. To keep an initial clone small, set `GIT_LFS_SKIP_SMUDGE=1` before cloning. + +CI checks real CPU forward/backward execution, both import styles and device-transfer failure handling. The transfer helper preserves graph identity and keeps the current attribute if conversion fails; earlier successful transfers are not rolled back. Full historical training and GPU execution are separate reproduction tasks. + +## Development + +Run the tests above before opening a PR. For a bug report, include the smallest framework that reproduces it, the command, package versions and expected versus actual behavior. Keep benchmark changes accompanied by split definitions, seeds and run logs. + +## Related projects and license + +[AFGCN](https://github.com/lmlearning/AFGCN) · [ExplainableArgGCN](https://github.com/lmlearning/ExplainableArgGCN) · [AFSubsample](https://github.com/lmlearning/AFSubsample) -## License - -See [LICENSE](LICENSE). +Code is available under the [MIT license](LICENSE). diff --git a/examples/gcn_demo.py b/examples/gcn_demo.py new file mode 100644 index 0000000..1a4f817 --- /dev/null +++ b/examples/gcn_demo.py @@ -0,0 +1,41 @@ +"""Fit the existing GCN to a four-argument illustrative framework on CPU.""" +import json +import os +os.environ.setdefault("DGLBACKEND", "pytorch") + +import dgl +import torch +import torch.nn.functional as F + +from GraphLib.model import GCN + + +def run_demo(): + torch.manual_seed(7) + torch.set_num_threads(1) + # a -> b -> c, plus isolated d. Grounded labels: a, c, d are accepted. + graph = dgl.add_self_loop(dgl.graph(([0, 1], [1, 2]), num_nodes=4)) + features = torch.eye(4) + labels = torch.tensor([[1.0], [0.0], [1.0], [1.0]]) + model = GCN(graph, in_feats=4, n_hidden=8, n_classes=1, + n_layers=1, activation=F.relu, dropout=0.0) + optimizer = torch.optim.Adam(model.parameters(), lr=0.03) + initial_loss = F.binary_cross_entropy_with_logits(model(features), labels).item() + for _ in range(40): + optimizer.zero_grad() + loss = F.binary_cross_entropy_with_logits(model(features), labels) + loss.backward() + optimizer.step() + model.eval() + with torch.no_grad(): + logits = model(features) + final_loss = F.binary_cross_entropy_with_logits(logits, labels).item() + return { + "example": "illustrative training fit, not a held-out benchmark", + "arguments": 4, "attacks": 2, "logits_shape": list(logits.shape), + "initial_loss": round(initial_loss, 6), "final_loss": round(final_loss, 6), + } + + +if __name__ == "__main__": + print(json.dumps(run_demo(), indent=2)) diff --git a/requirements-cpu.txt b/requirements-cpu.txt new file mode 100644 index 0000000..74b9793 --- /dev/null +++ b/requirements-cpu.txt @@ -0,0 +1,7 @@ +# CPU demo/runtime, tested with Python 3.11 on Linux and Windows. +--extra-index-url https://download.pytorch.org/whl/cpu +torch==2.7.0+cpu +dgl==1.1.2 +numpy==1.26.4 +scipy==1.15.3 +networkx==3.4.2 diff --git a/tests/test_cpu_model.py b/tests/test_cpu_model.py new file mode 100644 index 0000000..3c4d75e --- /dev/null +++ b/tests/test_cpu_model.py @@ -0,0 +1,29 @@ +import importlib +import subprocess +import sys +from pathlib import Path + +import pytest + +pytest.importorskip("torch") +pytest.importorskip("dgl") + +from examples.gcn_demo import run_demo + + +def test_real_gcn_forward_and_training_step(): + result = run_demo() + assert result["logits_shape"] == [4, 1] + assert 0 <= result["final_loss"] < result["initial_loss"] + + +@pytest.mark.parametrize("name", ["model", "util", "dglutil", "inference"]) +def test_library_imports_from_repository_root(name): + assert importlib.import_module("GraphLib." + name) + + +def test_legacy_script_imports_still_work(): + root = Path(__file__).resolve().parents[1] + result = subprocess.run([sys.executable, "-c", "import model, util, dglutil, inference"], + cwd=root / "GraphLib", capture_output=True, text=True) + assert result.returncode == 0, result.stderr