Coverage Summary for Class: Catalog (it.unibo.tuprolog.argumentation.parallel.node)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| Catalog |
100%
(27/27)
|
86.7%
(144/166)
|
97.7%
(212/217)
|
95.5%
(1562/1635)
|
| Catalog$Answering |
66.7%
(2/3)
|
35.7%
(5/14)
|
75%
(6/8)
|
75.7%
(84/111)
|
| Catalog$Built |
100%
(3/3)
|
83.3%
(5/6)
|
100%
(7/7)
|
100%
(42/42)
|
| Catalog$Companion |
100%
(2/2)
|
50%
(2/4)
|
100%
(2/2)
|
73.8%
(31/42)
|
| Catalog$evaluated$$inlined$groupingBy$1 |
0%
(0/3)
|
|
| Catalog$evaluated$$inlined$groupingBy$2 |
0%
(0/3)
|
|
| Catalog$Evaluating |
85.7%
(6/7)
|
81.2%
(13/16)
|
100%
(20/20)
|
94%
(205/218)
|
| Catalog$Job |
100%
(2/2)
|
|
100%
(4/4)
|
100%
(17/17)
|
| Catalog$Need |
|
| Catalog$Need$Explored |
100%
(1/1)
|
|
100%
(2/2)
|
100%
(7/7)
|
| Catalog$Need$Whole |
100%
(1/1)
|
|
100%
(1/1)
|
100%
(2/2)
|
| Catalog$Run |
100%
(5/5)
|
100%
(2/2)
|
100%
(6/6)
|
100%
(39/39)
|
| Catalog$Solving |
100%
(3/3)
|
50%
(3/6)
|
100%
(10/10)
|
97.8%
(91/93)
|
| Catalog$Stage |
100%
(1/1)
|
|
100%
(1/1)
|
100%
(30/30)
|
| Total |
86.9%
(53/61)
|
81.3%
(174/214)
|
97.5%
(271/278)
|
94.4%
(2110/2236)
|
package it.unibo.tuprolog.argumentation.parallel.node
import it.unibo.tuprolog.argumentation.AnswerQuery
import it.unibo.tuprolog.argumentation.Arg2pException
import it.unibo.tuprolog.argumentation.Flags
import it.unibo.tuprolog.argumentation.SyntaxProblem
import it.unibo.tuprolog.argumentation.core.Arg2pSolver
import it.unibo.tuprolog.argumentation.core.Arg2pSolverFactory
import it.unibo.tuprolog.argumentation.core.model.Graph
import it.unibo.tuprolog.argumentation.core.model.mining.activeBranch
import it.unibo.tuprolog.argumentation.core.model.mining.arguments
import it.unibo.tuprolog.argumentation.core.model.mining.graph
import it.unibo.tuprolog.argumentation.core.modules.entry.Arg2pModule
import it.unibo.tuprolog.argumentation.parallel.federation.EvaluationStats
import it.unibo.tuprolog.argumentation.parallel.federation.WorkerStats
import it.unibo.tuprolog.argumentation.parallel.message.Ack
import it.unibo.tuprolog.argumentation.parallel.message.Add
import it.unibo.tuprolog.argumentation.parallel.message.Address
import it.unibo.tuprolog.argumentation.parallel.message.AnswerResponse
import it.unibo.tuprolog.argumentation.parallel.message.Attack
import it.unibo.tuprolog.argumentation.parallel.message.AttacksFound
import it.unibo.tuprolog.argumentation.parallel.message.Build
import it.unibo.tuprolog.argumentation.parallel.message.Configure
import it.unibo.tuprolog.argumentation.parallel.message.DirectAttack
import it.unibo.tuprolog.argumentation.parallel.message.EvaluationResponse
import it.unibo.tuprolog.argumentation.parallel.message.Explore
import it.unibo.tuprolog.argumentation.parallel.message.Interest
import it.unibo.tuprolog.argumentation.parallel.message.KbMessage
import it.unibo.tuprolog.argumentation.parallel.message.Load
import it.unibo.tuprolog.argumentation.parallel.message.NO_GRAPH
import it.unibo.tuprolog.argumentation.parallel.message.Publish
import it.unibo.tuprolog.argumentation.parallel.message.Rebuild
import it.unibo.tuprolog.argumentation.parallel.message.RequireAnswer
import it.unibo.tuprolog.argumentation.parallel.message.RequireEvaluation
import it.unibo.tuprolog.argumentation.parallel.message.RequireSolution
import it.unibo.tuprolog.argumentation.parallel.message.Reset
import it.unibo.tuprolog.argumentation.parallel.message.Response
import it.unibo.tuprolog.argumentation.parallel.message.SolutionResponse
import it.unibo.tuprolog.argumentation.parallel.message.Stop
import it.unibo.tuprolog.argumentation.parallel.message.Subscribe
import it.unibo.tuprolog.argumentation.parallel.partition.Partitioning
import it.unibo.tuprolog.argumentation.parallel.partition.TheoryPartitioner
import it.unibo.tuprolog.core.Atom
import it.unibo.tuprolog.core.Integer
import it.unibo.tuprolog.core.Struct
import it.unibo.tuprolog.core.Term
import it.unibo.tuprolog.core.Var
import it.unibo.tuprolog.core.operators.OperatorSet
import it.unibo.tuprolog.core.parsing.ParseException
import it.unibo.tuprolog.core.parsing.parse
import it.unibo.tuprolog.solve.Solver
import it.unibo.tuprolog.solve.channel.OutputChannel
import it.unibo.tuprolog.theory.Theory
import it.unibo.tuprolog.theory.parsing.parse
import it.unibo.tuprolog.unify.Unificator
import kotlin.time.TimeMark
import kotlin.time.TimeSource
import it.unibo.tuprolog.core.List as PlList
import it.unibo.tuprolog.solve.Solution as LogicSolution
/**
* The catalog of a distributed solver: it holds the theory and partitions it among the workers (see
* [TheoryPartitioner], at most [parts] partitions if given, at most as many as the network can run otherwise), each
* owning some of the rules, gathers the graph they build (see [it.unibo.tuprolog.argumentation.parallel.message.KbMessage]
* for the protocol), and solves the goals of its clients on a solver of arg2p, with [flags] and the flags of the theory:
* 1. each worker is sent its rules and the flags, and tells the premises and the conclusions of its rules; every
* worker is then told those of all;
* 2. the workers build the arguments, sending each one to the workers that build on it and publishing it here: those
* of all their rules, or, in query mode (as the standard solver, see the module queryExploration), those the labels
* of the statements a goal evaluates depend on, as the semantics declares (directional or connected), explored from
* the workers that conclude them;
* 3. once every Build and Explore is acknowledged, the arguments are all published: the workers find the direct attacks
* on their own (with metaConflicts, where the conflicts are arguments as well, the solver of the catalog finds them);
* 4. the goal is solved as the standard solver solves it, but for the graph: the graph builder of the rules and query
* mode of the solver (see [graphModules]) save the arguments and the attacks the workers built, the graph store
* extends the attacks to the arguments built on the attacked ones, and the solver derives the other arguments,
* changes the graph by its extensions and labels it (every semantics, every graph extension).
*
* The catalog knows the shared part of the theory (the flags, the preferences, the conflicts, the Prolog predicates),
* not the rules, and relays no argument. A goal needing a graph the workers did not build (the whole graph, or in query
* mode the statements it explores) is solved again from its start once they built it, as is a goal whose solutions are
* asked for after the solver solved another one; an evaluation of a statement reads the labels of the arguments for it
* (see answerQuery/4). The graph is built again when the theory changes, when the network loses some of the workers of a
* graph being built ([Rebuild]), when the flags change ([Configure]), and when a goal needs a graph of the other kind
* (whole, or in query mode); in query mode, it grows as later goals need more of it. The evaluations stopped by their
* clients give up the graph being built for them ([Stop]).
*/
internal class Catalog(
private val network: Network,
private val parts: Int? = null,
private var flags: Flags = Flags.defaults(),
) : Node {
private val theory = StringBuilder()
private var changed = true
private var workers = 0
// The theory split for as many workers as the network ran when it was split, and how the solver builds the graph:
// by the catalog alone (a graph not built from rules), the attacks by the solver (with metaConflicts), and the
// locality query mode explores (null for the whole graph)
private var partitioning: Partitioning? = null
private var partitionedFor: Int? = null
private var central = false
private var centralAttacks = false
private var partial: String? = null
// The graph built: whether it is (complete, for the theory as it is), its locality (null for the whole graph), and
// the statements explored so far, or to explore once the workers are ready
private var built = false
private var locality: String? = null
private val explored = mutableListOf<Term>()
private val pending = mutableListOf<Term>()
// The construction of the graph: the stage it is at, the replies it still waits for (the subscriptions, the
// acknowledgements of the Builds and Explores, the attacks of each worker), the premises and conclusions of each
// worker
private enum class Stage { READY, SUBSCRIBING, BUILDING, ATTACKING }
private var stage = Stage.READY
private var awaited = 0
private val interests = mutableListOf<Interest>()
// The arguments published, by their global identifiers, and their supports
private val arguments = mutableMapOf<String, Term>()
// The arguments of the graph built, those the workers published, for the tests
internal val argumentsBuilt: Int get() = arguments.size
// The arguments of the graph labelled, those derived from the others included, as terms, for the tests
internal val graphArguments: List<Term> get() = solver.arguments(solver.activeBranch()).map { it.termRepresentation() }
private val supports = mutableListOf<Pair<String, String>>()
// The direct attacks the workers found
private val directAttacks = mutableListOf<DirectAttack>()
// The graph built, as the modules of the solver save it: the arguments and their supports, the attacks (none if the
// workers do not find them); and what the goal solved needs that is not built yet
private var saved: Pair<Term, Term>? = null
private var savedAttacks: Term? = null
private var need: Need? = null
// What a goal needs the workers to build: the whole graph, or the arguments for a statement in query mode
private sealed interface Need {
data object Whole : Need
data class Explored(
val statement: Term,
) : Need
}
// The goals asked for, solved in turn: the jobs waiting, the goals solved by the clients (the last ones), the one
// whose solutions the solver is computing, and what it printed computing them
private val jobs = ArrayDeque<Job>()
private val runs = LinkedHashMap<String, Run>()
private var current: Run? = null
private val printed = StringBuilder()
// Whether the solver holds the labels of the graph built (an evaluation of a statement labelled it, and no goal was
// solved since), and the arguments of the graph last reported (see EvaluationStats)
private var labelled = false
private var argumentsReported = 0
private var solver = newSolver()
private val unificator = Unificator.default
// The arguments for a statement read, by label, and the size of the graph labelled
private val argumentsLabelled = Goal("graphStore::argumentLabel(L, A), argument::claim(A, S)", solver.operators)
private val size =
Goal(
"graphStore::argumentIds(Ids), length(Ids, Arguments), findall(T, graphStore::attackById(T, _, _, _), Ts), " +
"length(Ts, Attacks)",
solver.operators,
)
// A message handled: an error (a theory that cannot be read, or that its profile refuses) answers the jobs waiting
// with it, and the theory is read anew for the next ones
override fun receive(message: KbMessage) {
try {
handle(message)
} catch (error: Exception) {
jobs.toList().also { jobs.clear() }.forEach { network.send(it.replyTo, it.failed(error.message ?: error.toString())) }
stage = Stage.READY
changed = true
need = null
}
}
private fun handle(message: KbMessage) {
when (message) {
Reset -> change { theory.clear() }
// a graph lost some of its workers: built again now if being built, at the next goal needing it otherwise
// (its workers are those of a site that left)
Rebuild -> if (stage != Stage.READY) start(explored.toList(), whole = locality == null) else built = false
is Load -> change { theory.clear().append(message.theory).append('\n') }
is Configure ->
change {
flags = Flags.parse(message.flags)
solver = newSolver()
}
Stop -> if (stage != Stage.READY) stop()
is Add -> change { theory.append(message.elem.trimEnd().removeSuffix(".")).append(".\n") }
is RequireEvaluation ->
try {
enqueue(Evaluating(message, Struct.parse(message.elem, solver.operators)))
} catch (error: ParseException) {
val response =
EvaluationResponse(emptyList(), emptyList(), emptyList(), "Not a statement: ${message.elem} (${error.message})")
network.send(message.replyTo, response)
}
is RequireSolution -> {
val run =
if (message.goal == null) {
runs[message.run]
} else {
Run { goal(message.goal, it) }.also {
runs[message.run] = it
if (runs.size > MAX_RUNS) runs.remove(runs.keys.first())
}
}
if (run == null) {
network.send(message.replyTo, SolutionResponse(message.run, null, NO_GRAPH, false, "", "This goal is no longer solved"))
} else {
enqueue(Solving(message.run, run, message.replyTo))
}
}
is RequireAnswer -> enqueue(Answering(message))
is Subscribe -> {
interests += Interest(message.worker, message.premises, message.conclusions)
if (--awaited == 0) {
stage = Stage.BUILDING
awaited = workers
repeat(workers) { network.send(Address.worker(it), Build(interests.toList(), locality)) }
explore(pending.toList())
pending.clear()
}
}
is Publish -> {
message.arguments.forEach { argument ->
// saved as the argument builder saves them, the last premise first: the graph store gives them
// back in the order of the worker
argument.supports.asReversed().forEach { supports += it to argument.id }
arguments[argument.id] = argument.argument
}
network.send(Address.worker(message.worker), Ack(-1))
}
is Ack -> if (stage == Stage.BUILDING && --awaited == 0) published()
is AttacksFound -> {
directAttacks += message.attacks
if (--awaited == 0) finished()
}
else -> Unit
}
}
private fun change(update: () -> Unit) {
update()
changed = true
}
// The solver of the catalog, with the flags: its theory given before the first job (see prepare)
private fun newSolver() =
Arg2pSolverFactory.solverOf(
Arg2pSolver.of(listOf(Arg2pModule, flags.library()), graphModules(Built())),
"",
output = OutputChannel.of { printed.append(it) },
)
// The jobs waiting dropped (their clients were told they stopped), and the graph being built for them given up: a
// session of its own, with no worker, so that nothing more of it reaches the catalog
private fun stop() {
jobs.clear()
need = null
current = null
network.building(0)
forget()
stage = Stage.READY
}
private fun enqueue(job: Job) {
jobs += job
work()
}
// The jobs waiting done in turn, while no graph is being built: a job needing a graph the workers did not build
// waits for them to build it (then is done from its start), the theory read anew if it changed
private fun work() {
while (stage == Stage.READY && jobs.isNotEmpty()) {
if (changed) prepare()
val job = jobs.first()
// a goal solved from its start: the context marked, as a goal labelling a graph resets it
if (current !== job.run) solver.solveOnce("context_assert($MARK)")
val answer =
try {
job.shortcut() ?: next(job.run).let { solution ->
val wanted = need ?: return@let job.answer(solution)
need = null
return build(wanted)
}
} catch (error: Arg2pException) {
job.failed(error.message ?: error.toString())
}
jobs.removeFirst()
// the statistics of a job labelling or reading a graph, known by its client once answered
if (job is Evaluating || !solver.holds("context_check($MARK)")) evaluated(job.since)
network.send(job.replyTo, answer)
}
}
// The next solution of [run]: solved from its start if the solver solved another goal since, the solutions given
// already left out (and what was printed computing them); null if it has no more, or needs a graph not built yet
private fun next(run: Run): LogicSolution? {
if (current !== run) {
current = run
labelled = false
printed.clear()
run.solutions = solver.solve(run.goal(solver)).iterator()
repeat(run.given) { pull(run) ?: return null }
}
return pull(run)?.also { run.given++ }
}
private fun pull(run: Run): LogicSolution? {
val solutions = run.solutions ?: return null
val solution = if (solutions.hasNext()) solutions.next() else null
if (need != null) current = null
return solution.takeIf { need == null }
}
// Whether [run] has more solutions: if knowing needs a graph not built yet, it is built when they are asked for
private fun hasNext(run: Run): Boolean {
val more = run.solutions?.hasNext() ?: false
if (need == null) return more
need = null
current = null
return true
}
// The solver given the theory, changed: the part of the catalog (see TheoryPartitioner), and how it builds the graph;
// the graph of the theory before forgotten
private fun prepare() {
forget()
val partitioning = partitioning()
solver.resetStaticKb()
solver.loadStaticKb(Theory.parse(partitioning.catalog, solver.operators))
solver.solveOnce("context_reset, parser:::convertAllRules(_)")
central = !solver.holds("structuredWhole::builder(B), B == ruleGraph")
centralAttacks = central || solver.holds("setting(metaConflicts)")
partial = if (central) null else locality()
changed = false
labelled = false
current = null
}
// The theory split for as many workers as the network runs: split again if they changed since
private fun partitioning(): Partitioning {
val capacity = parts ?: network.capacity
val known = partitioning?.takeIf { !changed && partitionedFor == capacity }
return known ?: TheoryPartitioner.partition(theory.toString(), capacity, flags).also {
partitioning = it
partitionedFor = capacity
}
}
// [need] built by the workers: the whole graph, the statement explored further in query mode on the graph built,
// or explored on a graph built anew
private fun build(need: Need) {
when {
need is Need.Explored && built && locality != null && locality == partial -> {
stage = Stage.BUILDING
built = false
awaited = 0
explore(listOf(need.statement))
if (awaited == 0) published()
}
need is Need.Explored -> start(listOf(need.statement), whole = false)
else -> start(emptyList(), whole = true)
}
}
// Whether the graph built has what [need] asks for
private fun met(need: Need) =
built &&
when (need) {
Need.Whole -> locality == null
is Need.Explored -> locality == partial && (locality == null || explored.any { covers(it, need.statement) })
}
// The workers loaded with their rules and the flags (the theory split again if they changed), and the locality of
// the graph chosen (the whole graph if [whole]): [goals] are explored once the workers are ready. A graph built
// otherwise than from rules (an abstract framework, see graphBuilder) is built by the solver alone.
private fun start(
goals: List<Term>,
whole: Boolean,
) {
val partitioning = partitioning()
locality = if (whole) null else partial
val count = if (central) 0 else partitioning.partitions.size
network.building(count)
repeat(count) { network.send(Address.worker(it), Load(partitioning.theoryOf(it), flags.toString())) }
forget()
workers = count
if (locality != null) pending += goals
if (workers == 0) return published()
stage = Stage.SUBSCRIBING
awaited = workers
}
// The graph built forgotten: its workers, what they told and built
private fun forget() {
workers = 0
built = false
interests.clear()
explored.clear()
pending.clear()
arguments.clear()
supports.clear()
directAttacks.clear()
argumentsReported = 0
}
// The part of the graph the labels of the arguments for a statement depend on, as the semantics declares it (see
// the module structuredPartial): directional or connected in query mode, on a graph built from the rules; null for
// the whole graph
private fun locality(): String? =
solver
.solveOnce(
"setting(queryMode), structuredWhole::builder(B), B == ruleGraph, setting(argumentLabellingMode, S), " +
"S::locality(L), L \\== whole_graph",
).takeIf { it.isYes }
?.substitution
?.getByName("L")
?.toString()
// [goals] explored by the workers whose rules conclude them, each Explore acknowledged
private fun explore(goals: List<Term>) {
explored += goals
interests.forEach { interest ->
val theirs = goals.filter { goal -> interest.conclusions.any { unificator.match(it, goal) } }
if (theirs.isNotEmpty()) {
awaited++
network.send(Address.worker(interest.worker), Explore(-1, theirs))
}
}
}
// The arguments all published: the workers find the attacks, but with metaConflicts
private fun published() {
directAttacks.clear()
if (workers == 0 || centralAttacks) return finished()
stage = Stage.ATTACKING
awaited = workers
repeat(workers) { network.send(Address.worker(it), Attack) }
}
// The graph built, kept as the modules of the solver save it (the attacks as attackBuilder saves them, none if the
// workers did not find them): the jobs waiting are done on it
private fun finished() {
val supported =
supports.map { (support, argument) ->
Struct.of("support", arguments.getValue(support), arguments.getValue(argument))
}
val attacks =
directAttacks.distinct().map { (type, attacker, target) ->
val attacked = arguments.getValue(target)
Struct.of("attack", Atom.of(type), arguments.getValue(attacker), attacked, attacked)
}
saved = PlList.of(arguments.values) to PlList.of(supported)
savedAttacks = PlList.of(attacks).takeUnless { centralAttacks }
built = true
labelled = false
stage = Stage.READY
work()
}
// The statistics of the job just done, waiting since [since]: what each worker built, read from the global
// identifiers of its arguments (its partition first) and of the arguments its attacks target
private fun evaluated(since: TimeMark) {
val published = arguments.keys.groupingBy { it.substringBefore(':').toInt() }.eachCount()
val found = directAttacks.distinct().groupingBy { it.target.substringBefore(':').toInt() }.eachCount()
val built = List(workers) { WorkerStats(it, network.siteOf(Address.worker(it)), published[it] ?: 0, found[it] ?: 0) }
val (graphArguments, graphAttacks) = size.solutions(solver).first().let { it.count("Arguments") to it.count("Attacks") }
val added = arguments.size - argumentsReported
argumentsReported = arguments.size
network.evaluated(
EvaluationStats(
if (central) "catalog" else locality ?: "whole_graph",
graphArguments,
graphAttacks,
added,
built,
since.elapsedNow().inWholeMicroseconds / 1000.0,
),
)
}
private fun Map<String, Term>.count(name: String) = (getValue(name) as Integer).intValue.toInt()
// The graph of the last solution, none for a solution that labels none
private fun mined(): Graph =
try {
solver.graph()
} catch (_: Exception) {
NO_GRAPH
}
// The graph built, for the modules of the solver: what a goal needs and the workers did not build is asked for
private inner class Built : BuiltGraph {
override fun arguments(need: Term): Pair<Term, Term>? {
val wanted = if (need is Struct && need.functor == "explore") Need.Explored(need[0]) else Need.Whole
if (!met(wanted)) {
this@Catalog.need = wanted
return null
}
return saved
}
override val attacks: Term? get() = savedAttacks
}
// A goal solved for a client, read by [read] (with the operators of the theory) the first time it is solved: the
// solutions given so far and the characters printed computing them, and the solutions of the solver while it
// solves this goal
private class Run(
private val read: (OperatorSet) -> Struct,
) {
private var goal: Struct? = null
var given = 0
var printed = 0
var solutions: Iterator<LogicSolution>? = null
fun goal(solver: Solver): Struct = goal ?: read(solver.operators).also { goal = it }
}
// A job: the next solution of [run], answered to [replyTo] (shortcut if the solver need not solve it), or the
// error that kept it from one; it waits [since]
private abstract inner class Job(
val replyTo: String,
) {
abstract val run: Run
val since = TimeSource.Monotonic.markNow()
open fun shortcut(): KbMessage? = null
abstract fun answer(solution: LogicSolution?): KbMessage
abstract fun failed(error: String): KbMessage
}
// The next solution of the goal of the client, with the graph of its resolution and what the solver printed
private inner class Solving(
private val id: String,
override val run: Run,
replyTo: String,
) : Job(replyTo) {
override fun answer(solution: LogicSolution?): KbMessage {
val graph = solution?.let { mined() } ?: NO_GRAPH
val more = solution != null && hasNext(run)
val output = printed.substring(minOf(run.printed, printed.length))
run.printed += output.length
return SolutionResponse(id, solution, graph, more, output)
}
override fun failed(error: String) = SolutionResponse(id, null, NO_GRAPH, false, "", error)
}
// The statements for a query by label, as the standard solver answers them
private inner class Answering(
request: RequireAnswer,
) : Job(request.replyTo) {
private val query by lazy { AnswerQuery(term(request.query, solver.operators)) }
override val run = Run { query.goal }
override fun answer(solution: LogicSolution?): KbMessage =
if (solution?.isHalt == true) {
failed(solution.exception?.message ?: solution.toString())
} else {
AnswerResponse(query.answer(solution?.takeIf { it.isYes }))
}
override fun failed(error: String) = AnswerResponse(null, error)
}
// The arguments for a statement by label: the graph labelled for it (see answerQuery/4), or as it was if the solver
// labelled the graph built and it has them
private inner class Evaluating(
private val request: RequireEvaluation,
private val statement: Term,
) : Job(request.replyTo) {
override val run = Run { Struct.of("answerQuery", statement, Var.anonymous(), Var.anonymous(), Var.anonymous()) }
override fun shortcut(): KbMessage? = if (labelled && met(needed)) labels() else null
override fun answer(solution: LogicSolution?): KbMessage {
if (solution == null) return EvaluationResponse(emptyList(), emptyList(), emptyList())
if (solution.isHalt) return failed(solution.exception?.message ?: solution.toString())
labelled = true
return labels()
}
override fun failed(error: String) = EvaluationResponse(emptyList(), emptyList(), emptyList(), error)
// what answering the statement needs: the whole graph, unless query mode explores it
private val needed: Need get() = if (partial == null) Need.Whole else Need.Explored(statement)
private fun labels(): EvaluationResponse {
// the statement bound as a term, its variables apart from those of the goal
val found =
argumentsLabelled
.solutions(solver, "S" to statement)
.map { it.getValue("L").toString() to it.getValue("A").toString() }
.distinct()
val labelled = { label: String -> found.filter { it.first == label }.map { Response(request.elem, listOf(it.second)) } }
val und = if (found.isEmpty()) listOf(Response(request.elem, emptyList())) else labelled("und")
return EvaluationResponse(labelled("in"), labelled("out"), und)
}
}
private companion object {
// The goals of the clients kept, to give their next solutions
const val MAX_RUNS = 16
// The fact marking the context of the solver before a goal: gone after it if the goal labelled a graph
const val MARK = "catalog_mark"
// A query, Prolog text, read as the standard solver reads it
fun term(
text: String,
operators: OperatorSet,
): Term = SyntaxProblem.parsing("Query") { Term.parse(text.trim().removeSuffix("."), operators) }
// A goal, Prolog text, read as the standard solver reads it
fun goal(
text: String,
operators: OperatorSet,
): Struct = term(text, operators).let { it as? Struct ?: throw Arg2pException("Not a goal: $text") }
}
}