Tabling: No Permission to Update Variant

Hello,

I am using tabling to improve on the performance of my program. I am facing some problems, and would really appreciate some help.

My program calls a set of entangled reasoners; that call each other, and cause cycles. My approach was to use guards to not allow a goal to call itself through zero or more predicates. The following shows two reasoners that need to call each other, and the guards I have setup to stop cyclic calls.

:- thread_local pending/1.

reasoner_1(Goal):- 
	% make sure this goal is not being evaluated by reasoner_1/1 earlier in the search tree
	\+ pending(reasoner_1(Goal)), 
	% if not, it is now, so mark it as "pending proof"
	assert(pending(reasoner_1(Goal))),
	(
		% either prove using reasoner_2/1 and release the guard
		(
			reasoner_2(Goal), 
			retract(pending(reasoner_1(Goal)))
		);
		% or in case reasoner_2/1 failed, release the guard and fail
		(
			(pending(reasoner_1(Goal)) -> retract(pending(reasoner_1(Goal))); true),
			fail
		)
	).
	
reasoner_2(Goal):- 
	% make sure this goal is not being evaluated by reasoner_2/1 earlier in the search tree
	\+ pending(reasoner_2(Goal)), 
	% if not, it is now, so mark it as "pending proof"
	assert(pending(reasoner_2(Goal))),
	(
		% either prove using reasoner_1/1 and release the guard
		(
			reasoner_1(Goal), 
			retract(pending(reasoner_2(Goal)))
		);
		% or in case reasoner_1/1 failed, release the guard and fail
		(
			(pending(reasoner_2(Goal)) -> retract(pending(reasoner_2(Goal))); true),
			fail
		)	
	).

The following are example executions.
?- reasoner_1(1).
false.
?- reasoner_1(G).
false.

Months later, I was introduced to the wonder world of tabling. So I added the following two lines to the beginning of the previous code:
:- table reasoner_1/1 as incremental.
:- dynamic([pending/1], [incremental(true)]).

Now, here are sample execution outputs.

?- reasoner_1(1).
ERROR: '$idg_changed'/1: No permission to update variant `user:reasoner_1(1)'
ERROR: No permission to update variant `user:reasoner_1(1)'
ERROR: In:
ERROR:   [21] assert(pending(reasoner_1(1)))
ERROR:   [20] reasoner_1(1) at c:/workspace/prolog_development/testing_other_prolog_stuff/tabling/tabling_sequential.pl:9
ERROR:   [19] call('$toplevel':<closure>(reasoner_1/1)(1)) at d:/programfiles/swipl/boot/init.pl:395
ERROR:   [18] reset('$toplevel':call(...),_5934,_5936) at d:/programfiles/swipl/boot/init.pl:471
ERROR:   [17] '$tabling':delim(ret,'$toplevel':call(...),11962876,[]) at d:/programfiles/swipl/boot/tabling.pl:468
ERROR:   [16] '$tabling':activate(ret,'$toplevel':call(...),<trie>(0000016663A0E170),11962876) at d:/programfiles/swipl/boot/tabling.pl:454
ERROR:   [15] '$tabling':run_leader(ret,'$toplevel':call(...),<trie>(0000016663A0E170),11815544,_6076,_6078) at d:/programfiles/swipl/boot/tabling.pl:439
ERROR:   [14] setup_call_catcher_cleanup('$tabling':'$idg_set_current'(_6128,<trie>(0000016663A0E170)),'$tabling':run_leader(ret,...,<trie>(0000016663A0E170),11815544,_6148,_6150),_6116,'$tabling':finished_leader(_6160,_6162,11815544,...)) at d:/programfiles/swipl/boot/init.pl:539
ERROR:   [13] '$tabling':create_table(<trie>(0000016663A0E170),ret,user:reasoner_1(1),'$toplevel':call(...)) at d:/programfiles/swipl/boot/tabling.pl:318
ERROR:   [12] catch('$tabling':create_table(<trie>(0000016663A0E170),ret,...,...),deadlock,'$tabling':restart_tabling(<closure>(reasoner_1/1),...,...)) at d:/programfiles/swipl/boot/init.pl:457
ERROR:   [11] '$tabling':start_tabling(<closure>(reasoner_1/1),user:reasoner_1(1),'$toplevel':call(...)) at d:/programfiles/swipl/boot/tabling.pl:303
ERROR:   [10] '$wrap$reasoner_1'(1)1-st clause of '$wrap$reasoner_1'/1 <no source>
ERROR:    [9] <user>
?- reasoner_1(G).
ERROR: '$idg_changed'/1: No permission to update variant `user:reasoner_1(_7164)'
ERROR: No permission to update variant `user:reasoner_1(_8328)'
ERROR: In:
ERROR:   [21] assert(pending(reasoner_1(_8380)))
ERROR:   [20] reasoner_1(_8404) at c:/workspace/prolog_development/testing_other_prolog_stuff/tabling/tabling_sequential.pl:9
ERROR:   [19] call('$toplevel':<closure>(reasoner_1/1)(_8438)) at d:/programfiles/swipl/boot/init.pl:395
ERROR:   [18] reset('$toplevel':call(...),_8464,_8466) at d:/programfiles/swipl/boot/init.pl:471
ERROR:   [17] '$tabling':delim(ret(_8516),'$toplevel':call(...),11962552,[]) at d:/programfiles/swipl/boot/tabling.pl:468
ERROR:   [16] '$tabling':activate(ret(_8566),'$toplevel':call(...),<trie>(0000016663A0D590),11962552) at d:/programfiles/swipl/boot/tabling.pl:454
ERROR:   [15] '$tabling':run_leader(ret(_8620),'$toplevel':call(...),<trie>(0000016663A0D590),11816216,_8614,_8616) at d:/programfiles/swipl/boot/tabling.pl:439
ERROR:   [14] setup_call_catcher_cleanup('$tabling':'$idg_set_current'(_8670,<trie>(0000016663A0D590)),'$tabling':run_leader(...,...,<trie>(0000016663A0D590),11816216,_8690,_8692),_8658,'$tabling':finished_leader(_8702,_8704,11816216,...)) at d:/programfiles/swipl/boot/init.pl:539
ERROR:   [13] '$tabling':create_table(<trie>(0000016663A0D590),ret(_8748),user:reasoner_1(_8758),'$toplevel':call(...)) at d:/programfiles/swipl/boot/tabling.pl:318
ERROR:   [12] catch('$tabling':create_table(<trie>(0000016663A0D590),...,...,...),deadlock,'$tabling':restart_tabling(<closure>(reasoner_1/1),...,...)) at d:/programfiles/swipl/boot/init.pl:457
ERROR:    [9] <user>
ERROR: 
ERROR: Note: some frames are missing due to last-call optimization.
ERROR: Re-run your program in debug mode (:- debug.) to get more detail.
^  Exception: (12) catch('$tabling':create_table(<trie>(0000016663A0D590), ret(_6704), user:reasoner_1(_6704), '$toplevel':call(<closure>(reasoner_1/1)(_6704))), deadlock, '$tabling':restart_tabling(<closure>(reasoner_1/1), user:reasoner_1(_6704), '$toplevel':call(<closure>(reasoner_1/1)(_6704)))) ? creep

I have a very conservative programming approach. I try not to mess code too much when adding new stuff, so I thought it is best I leave my guards around while adding tabling; and when things look fine, I dispense with the guards leaving only tabling.

I couldn’t decipher much of the error message which from my limited knowledge looks like it has been written in the old language of Mordor, but I saw a familiar word: “deadlock”. Is mixing my guards with tabling causing this? in which case do I need to remove them before trying tabling?

Thanks.

Incremental tabling is meant to define a predicate without side effects from some facts. And then these facts can be changed, and the predicate gets reevaluated.

Your predicates reasoner_1 and reasoner_2 have side effects, through assert/1 and retract/1, which I guess is not in the scope of incremental tabling.

I don’t understand.

From my understanding, when a predicate is marked for incremental tabling, it means that it re-evaluates its tables when facts change in the knowledge base (I mean those facts that are evaluated as part of evaluating the predicate in question).

Doesn’t this imply that incremental tabling supports assert and retract? What other ways are there to change facts in the knowledge base if not by asserting/retracting them?

Lets say p is the tabled predicat and q is the fact that is updated.
You get into an infinite loop, if p depends on q and p updates q.

So I guess it is forbidden.

But you need to check what is the offending variant key,
maybe you can disentangle the loop somehow.

Thank you :slight_smile:
I will proceed by removing the problematic assertions.

What is a variant key?

Thank you for taking the time to explain this :slight_smile:
It is clear to me now :slight_smile: