Hi all,
I have been benchmarking several rational tree comparison predicates (including Foreign C implementations) by fuzz-testing them against transitivity: generating random terms and searching for counterexamples (X<Y∧Y<Z∧¬(X<Z)).
Initially, I tried the natural idiom using repeat and call_with_time_limit/2:
Prolog
anti_transitivity(Sec, Pred, X, Y, Z) :-
catch(
call_with_time_limit(Sec, search_anti_trans(Pred, X, Y, Z)),
time_limit_exceeded,
writeln('Timeout.')
).
search_anti_trans(Pred, X, Y, Z) :-
repeat,
fuzzy(X),
fuzzy(Y),
call(Pred, C, X, Y), C = (<),
fuzzy(Z),
call(Pred, D, Y, Z), D = (<),
call(Pred, E, X, Z),
write(E), flush_output,
E \== (<).
However, this frequently failed to stop at the time limit and ran away indefinitely, leaving C-c C-c as the only way to interrupt the process. Occasionally, it would also surface an unhandled time_limit_exceeded exception directly at the top level despite the catch/3 wrapper.
I ended up ditching call_with_time_limit/2 and asynchronous signals altogether, switching to an explicit tail-recursive loop with a get_time/1 deadline check:
:- meta_predicate anti_transitivity(+, 3, _, _, _).
anti_transitivity(Sec, Pred, X, Y, Z) :-
get_time(T0),
Deadline is T0 + Sec,
search_anti_trans(Deadline, Pred, X, Y, Z).
search_anti_trans(_Deadline, Pred, X, Y, Z) :-
fuzzy(X),
fuzzy(Y),
call(Pred, C, X, Y), C = (<),
fuzzy(Z),
call(Pred, D, Y, Z), D = (<),
call(Pred, E, X, Z),
write(E), flush_output,
E \== (<),
!.
search_anti_trans(Deadline, Pred, X, Y, Z) :-
get_time(Now),
Now < Deadline,
!,
search_anti_trans(Deadline, Pred, X, Y, Z).
search_anti_trans(_Deadline, _Pred, _X, _Y, _Z) :-
nl,
writeln('No anti-transitive triples found.').
This completely solved the issue and runs 100% reliably across all my candidate predicates:
?- anti_transitivity(10, raw_comp, X, Y, Z).
?- anti_transitivity(1, ft_compare, X, Y, Z).
?- anti_transitivity(2, comp, X, Y, Z).
?- anti_transitivity(2, rt_ord_by_len, X, Y, Z).
?- anti_transitivity(2, prt_compare, X, Y, Z).
?- anti_transitivity(2, fct_compare, X, Y, Z).
?- anti_transitivity(1, pcft_compare, X, Y, Z).
?- anti_transitivity(5, prt_compare, X, Y, Z).
?- anti_transitivity(2, prt_min_compare, X, Y, Z).
?- anti_transitivity(1, mcoa_compare_with_heap, X, Y, Z).
While the explicit loop works cleanly, I am curious:
What prevents call_with_time_limit/2 from reliably interrupting tight backtracking loops involving frequent write/1 + flush_output/0 and foreign predicates?
Is there an idiomatic way to make call_with_time_limit/2 work predictably in such settings without replacing repeat with explicit recursion?