Modules
Arg2P is not a single monolithic theory: it is a set of modules, each providing one step of the argumentation pipeline. Modules are loaded on demand, which keeps memory usage low and makes the behaviour of the engine configurable — choosing a semantics, for instance, simply means dispatching to a different module.
You normally never call a module directly: the engine interface does it for you, guided by the flags. Calling one explicitly is useful when debugging, when running a single stage of the pipeline, or when extending the framework.
The modular model implemented here — fragmenting a theory into modules that coexist, interact and nest — is described in Calegari, Contissa, Pisano, Sartor and Sartor, Modular logic argumentation in Arg-tuProlog; see References.
Calling a module
Two operators dispatch a goal to a module:
Module::Goal % run Goal inside Module
Module:::Goal % same, but in a branched evaluation contextFor example, to run the abstract solver directly:
?- abstract::solve([a, b, c], [(a, b), (b, c)], I, O, U).:: versus :::
The difference matters when a stage can produce more than one result.
::runs the goal in the current context. Whatever the goal asserts stays in that context.:::branches the context first, so each solution gets its own private copy of the computed data.
This is how multi-extension semantics work: preferred, stable, cf2 and stage2 enumerate their
extensions by branching with :::, each branch holding one labelling. When you iterate over the solutions of
such a query, you are iterating over those branches.
The theory is the module user
The theory you write is a module of its own, user, and its predicates are its own: the modules of Arg2P keep
theirs inside, so a theory can define any predicate (member/2, holds/1, contrary/2, …) without changing
how the engine works, and a predicate the theory defines is the one its goals call, whatever a library names the
same way. The modules reach the theory only through user::Goal, the way they read its rules, preferences and
conflicts, and run its prolog/1 checks:
?- user::findall(X, member(X, [a, b]), L).The pipeline
A complete evaluation runs these stages in order. Each one is a module, selected by a flag where more than one implementation exists.
| Stage | Module | Selected by |
|---|---|---|
| Parse the theory into internal rules, in the languages of its profile | parser, aspicLanguage, abaLanguage, defeasibleLanguage, abstractLanguage | graphLanguage (set by graphBuildMode) |
| Build arguments, attacks and supports | ruleGraph, abstractGraph | graphBuilder (set by graphBuildMode) |
Change the graph, in a fixed order: add the arguments derived from the others (the copies of metaConflicts) with their attacks, then rebutRestriction, the preference mode, bp | conflictArguments, one per extension | graphExtension, metaConflicts |
| Label the arguments | one labeller per semantics | argumentLabellingMode |
| Label the statements | statement, statement_pass_through, statement_binary | statementLabellingMode |
The structuredWhole module runs the sequence on the whole graph, and structuredPartial implements the goal-directed variant
used when queryMode is enabled. That variant differs in the second stage alone: it builds the part of the graph the
query depends on (queryExploration), first through the arguments alone (their premises and possible attackers, and
for a connected semantics what they attack and support), then through what the steps changing the graph relate them
to (queryDependencies/2 and queryDependants/2 of the modules defining them), the attacks between
them after. The other stages are the same.
Available modules
Pipeline modules
Every module, with all its predicates, is in the Prolog modules reference.
| Module | Purpose |
|---|---|
structuredWhole | Orchestrates the evaluation of the whole graph. |
structuredPartial | Goal-directed evaluation used by answerQuery, through queryExploration and argumentSearch. |
abstract | The abstract AF entry points. |
parser | Translates the Arg2P language into the engine’s internal rule format. |
ruleGraph | Builds the argumentation graph from the rules: its arguments (argumentBuilder) and the attacks between them (attackBuilder). Those derived from them are added when the graph is changed (conflictArguments). |
abstractGraph | Builds the graph from the arguments and attacks a theory of the profile abstract states. |
attackRelation, attackCandidates | Define when an argument attacks another, and which arguments may. |
superiority | Implements argument ordering and comparators. |
utils | List and sorting helpers. |
debug | Prints graphs and labellings (buildLabelSets/0). |
module | Loads external .pl files, see external modules. |
Labelling modules
One per value of argumentLabellingMode (grounded, complete, preferred, stable, bp_grounded, …) and
one per value of statementLabellingMode. They all expose the same entry point, argumentLabelling/0 or
statementLabelling/0 respectively, which is what makes them interchangeable:
?- grounded::argumentLabelling.
?- statement::statementLabelling.Graph extension modules
standardPref, defeasiblePref, defeasibleAllPref, rebutRestriction, bp and abaPref all expose
modifyArgumentationGraph/0, and are applied after the graph has been built: conflictArguments first with
metaConflicts, as it adds arguments whose attacks the others change.
Inspecting intermediate results
Data produced by a stage is stored in the evaluation context and can be read back with context_check/1:
?- context_check(argument(A)).
?- context_check(attack(Type, Attacker, Attacked, On)).
?- context_check(in(A)).This is the most direct way to see what a single stage produced. The context holds the attacks as they are found,
each on the argument it targets: an attack on an argument also attacks every argument built on it, on the same
sub-argument, and graphStore::attack/4 reads them all, as the Kotlin graph()
helper does:
?- graphStore::attack(Type, Attacker, Attacked, On).Useful context predicates:
| Predicate | Content |
|---|---|
argument/1 | An argument built by the graph builder. |
attack/4 | attack(Type, Attacker, Attacked, On), as found (see graphStore::attack/4 for all of them). |
support/2 | support(Supporter, Supported). |
in/1, out/1, und/1 | The argument labelling. |
statIn/1, statOut/1, statUnd/1 | The statement labelling. |
sup/2 | Superiority relations declared in the theory. |
External modules
Theories kept in separate .pl files can be loaded and queried without adding them to the knowledge base:
module::call_module([Modules], Goal)Modules is a list of module names, resolved against the
modulesPath location, and Goal is solved against their combined content.
A full location can be given instead of a name, in which case modulesPath is ignored:
?- module::call_module(['greetings'], hello(X)).
?- module::call_module(['/home/me/theories/greetings.pl'], hello(X)).Remote modules
Modules are resolved as URLs, so they do not have to live on the local filesystem. modulesPath may be a base
URL, and a module may be given as a full URL:
modulesPath('https://example.org/theories').
?- module::call_module(['greetings'], hello(X)).
?- module::call_module(['https://example.org/theories/greetings.pl'], hello(X)).A URL does not need to end in .pl: anything carrying a scheme (https://...) is treated as a location rather than
as a module name, and so is a path ending in .pl. With no modulesPath, module names are resolved in the working
directory.
A remote module is fetched and loaded as part of your theory, so it is executed with the same trust as the rules you wrote yourself. Only point
modulesPathat a location you control, and preferhttps— content fetched over plainhttpcan be substituted in transit.
The modules are loaded into a separate solver, so their clauses do not pollute the theory under evaluation.
A module is read with the operators of Arg2P (:: and ::: included). A module that cannot be found or read raises
an existence error naming it and saying why, one that does not parse a syntax error; an error raised within the module
is raised as it is.
What a module sees
A module may be written in plain Prolog or in the Arg2P language, and a full
evaluation can be run over it, which makes call_module/2 a way to reason about a theory kept in a separate
file:
?- module::call_module(['theory'], arg2p::solve(d, Res)).
Res = [in(d)]Inside the call:
- the flags the calling solver was given apply (those of its
Flags, not those written in its theory), so the module is evaluated under the same semantics, ordering and preferences, unless the module states its own; - the theory of the calling solver is not visible: the module is evaluated on its own content alone;
- the resulting graph is written to the usual evaluation context, so it can be mined afterwards and is drawn by the solver of the playground like any other evaluation.
Evaluating a module replaces the current context, so the graph you inspect afterwards is the module’s, not the one built from the theory loaded in the solver.
Adding your own module
A module is an ArgLibrary that is also Loadable, as every PrologModule: its identifier() is the name
used after ::. Pass your libraries to the
factory to have them loaded alongside the built-in ones:
val solver = Arg2pSolverFactory.default(
theory = theory,
staticLibs = listOf(MyLibrary()), // always loaded
dynamicLibs = listOf(MyModule()), // loaded on first use
)Static libraries are materialised immediately; dynamic ones are resolved the first time a :: or ::: call
mentions them. The causality and actor solvers are built exactly this way.